kmjp's blog

競技プログラミング参加記です

yukicoder : No.1906 Twinkle Town

これ系苦手。
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;
	}
}

まとめ

類題いくつか見てきた気がするけど、なかなか解けない…。