これ系苦手。
https://yukicoder.me/problems/no/1906
問題
多くの家が並んでおり、初期状態では全て消灯している。
数列Aが与えられ、2人で交互にターンが来る以下のゲームを行う。
- 数列の中から1つ要素A[i]を選ぶ。
- 先手がA[i]を選んだ場合、家の端からA[i]個の家が点灯する
- 後手がA[i]を選んだ場合、家の端からA[i]個の家が消灯する
- 一度選んだ要素はAから取り除かれる。Aが空になったら終了。
先手は最後に点灯している家の数を最大化し、後手は最小化したい。
両者最適手を取ると、最終的に何個の家の電気が点くか。
解法
Aを昇順にソートし、その差の列をBとする。
両者の行動は、Bに対し以下の効果を及ぼす。
- Bの末尾の要素を選ぶと、その値の分、最終的に点灯・消灯が確定した家の数が増える
- Bの末尾以外の要素B[i]を選ぶと、B[i]とB[i+1]がマージされ、和を取った値1要素に置き換わる
この前提の下では、Nが奇数(2n+1)の場合、先手は以下のスコアを得られる。
- Bのうち末尾2k+1要素のうち、k+1個の要素を選んでその和の分点灯できる、という条件のもと、Bからn+1個の要素を選ぶ選び方
kが1個増えるたびに、選ぶ候補が2個増えるので、そのつど(まだ選んでない要素のうち)最大値を選んでいくと良い。
Nが偶数の場合、先手がAの最大値を選んだとして、あとは後手がどれだけ消灯できるかという問題を考えることで、Nが奇数のパターンに持ち込める。
int T,N,A[202020]; void solve() { int i,j,k,l,r,x,y; string s; cin>>T; while(T--) { cin>>N; FOR(i,N) cin>>A[i]; sort(A,A+N); int iseven=0; if(N%2==0) { iseven=A[N-1]; N--; } int sum=A[0]; multiset<int> S; for(i=1;i<N;i+=2) { x=A[i]-A[i-1]; y=A[i+1]-A[i]; S.insert(x); S.insert(y); sum+=*S.rbegin(); S.erase(S.find(*S.rbegin())); } if(iseven) sum=iseven-sum; cout<<sum<<endl; } }
まとめ
類題いくつか見てきた気がするけど、なかなか解けない…。