実際、2つのアイデアは書面でのみ異なる
理解できない場合は、コードを直接見てくださいqwq
タイトル
タイトル
n個のモンスターが円を形成し、各モンスターの体力はa、爆発ダメージはbです。
モンスターiが死亡した場合(ヘルスが0以下)、その隣のモンスターはb [i]のダメージを受けます
(i <nの場合、次のモンスターはi + 1です。i== nの場合、次のモンスターは1です)
場合は下隣接のモンスターが存在しない、何も起こりません
怪我をした後、次の隣のモンスターも死んだ場合、次の怪物は負傷し続けます(連鎖反応)
モンスターを選んで発砲できるたびに、そのモンスターのスタミナは1ポイント減少します
少なくともすべてのモンスターを解くために撃つ必要がある銃の数を尋ねる
問題解決のアイデア1
モンスターが最初に殺すように選ばれている限り、次のn-1が順番に殺されることがわかる。
だから主な問題は最初のモンスターを見つけることです
また、nの最大値は3e5であるため、各シミュレーションはO(n)の複雑さであり、全体的な時間の複雑さO(n 2)は完全に実現不可能です。
1時間以上考えた結果、循環配列を使用して接頭辞の合計を格納し、すべての回答をシミュレートできることがわかりました。
しかし、巡回配列は書くのが面倒なので、単純に倍長配列を直接開きます(3つの長い6e5はMLEを気にする必要はありません)。
読み込まれたデータは、2つの配列arとbrに格納されます。
そして、ar[i] == ar[i+n] && br[i] == br[i+n] ,i=1~n
使用済みの別の配列を開き、使用済み[i] は、モンスター番号1を殺すことからモンスター番号iまでのプロセスで使用する必要がある弾丸の数を記録します。
usedは、記録プロセス中に番号1のモンスターを殺すために使用された弾丸のみを記録し、処理プロセスは次のとおりです。
使用済み[1] = 0の特別な処理
i 2から開始して、i番目のモンスターの体力がi-1モンスターの爆発ダメージ以下である場合、それは前のモンスターが死ぬ限り、これも死ぬことを意味し、答えに寄与しません used[i] = used[i-1]
i番目のモンスターの体力がi-1番目のモンスターの爆発ダメージより大きい場合、死ぬにはar [i] -br [i-1]弾を追加する必要があることを意味します used[i] = used[i-1] + ar[i] - br[i-1]
もともとは循環配列だったため、2 * n(実際には2 * n-1のみ)まで処理する必要があります。
すべての処理が完了すると、すべての状況が出てきます
たとえば、i = 1のモンスターから始めることを選択します。
次に、最初にar [1]弾を使ってこのモンスターを殺します
その後、最初のものからnを殺し始めます、最後のモンスターの数はi = nです
そのため、プロセスで[n]使用される[1]弾が多くかかります
この状況に対する答えは ar[1] + used[n] - used[1]
一般化、i∈[1、n]の各ケースに対する答えは ar[i] + used[i+n-1] - used[i]
小さな出力を取る
手順1
使用した配列を処理する手順と回答を計算する手順を組み合わせる
次に、答えを処理する範囲はn〜2 * n-1
(280ms / 1000ms)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;
ll ar[600050],br[600050],used[600050];
void solve()
{
int n;
cin>>n;
ll ans=LINF;
for(int i=1;i<=n;i++)
{
cin>>ar[i]>>br[i];
ar[n+i]=ar[i];
br[n+i]=br[i];
}
used[1]=0;
for(int i=2;i<2*n;i++)
{
if(ar[i]<=br[i-1])
used[i]=used[i-1];
else
used[i]=used[i-1]+ar[i]-br[i-1];
if(i>=n)
ans=min(ans,used[i]-used[i-n+1]+ar[i-n+1]);
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T;cin>>T;
for(int t=1;t<=T;t++)
solve();
return 0;
}
問題解決のアイデア2
アイデア1はused[i+n] - used[i]
実際には固定値であることがわかります(これはi + n-1およびiではないことに注意してください)
この固定値は、最初のモンスターを殺すためのコスト、およびプロセスで費やす必要がある弾丸の総数を無視することを意味します
この固定値をkとすると、kを見つける方法はk = k + sum{max(0,ar[i]-br[i-1])}
この固定値では、最初のモンスターを殺して答えに追加するための最小コストを見つけるだけで済みます
最初のモンスターを殺すための最小コストはminnです
アイデア1の式を使用した同時解方程式が得られます
used[i+n] - used[i] + minn == used[i+n-1] - used[i] + ar[i]
それは
ar[i] - minn == used[i+n] - used[i+n-1] == used[i] - used[i-1]
それが理解されているように、AR [i]とミネソタ州違いは、ありused[i] - used[i-1]
、このステップ
そして、この違いは、特定のステップで消費する必要がある弾丸の数を表しています
ゲットminn == ar[i] - (used[i] - used[i-1])
i番目のモンスターの場合:
もしbr[i-1] >= ar[i]
前回の死後、i番目のモンスターが殺されることを説明する
したがって、kの計算では、i番目のモンスターを殺すためのコストは記録されません。
つまりused[i] - used[i-1] == 0
だからあなたは得ることができます minn = min(minn,ar[i])
もしbr[i-1] < ar[i]
前回の死後、i番目のモンスターは殺されないことを説明する
したがって、kの計算中に補足される弾丸の数として、ar [i]とbr [i-1]の差が追加されます。
つまりused[i] - used[i-1] == ar[i] - br[i-1]
だからあなたは得ることができます minn = min(minn,br[i-1])
最後に、2つの数値1とnの関係を判断します
手順2
(280ms / 1000ms)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;
ll ar[300050],br[300050];
void solve()
{
int n;
ll k=0,minn=LINF,d;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>ar[i]>>br[i];
d=ar[i]-br[i-1];
if(i)
{
if(d>0)
minn=min(minn,br[i-1]),k+=d;
else
minn=min(minn,ar[i]);
}
}
d=ar[0]-br[n-1];
if(d>0)
minn=min(minn,br[n-1]),k+=d;
else
minn=min(minn,ar[0]);
cout<<k+minn<<'\n';
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T;cin>>T;
for(int t=1;t<=T;t++)
solve();
return 0;
}