kmjp's blog

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

Codeforces #380 Div1 D. Financiers Game

うーん…。
http://codeforces.com/contest/737/problem/D

問題

N個の整数列Aがある。
この整数列に対し2人で交互に数字を取り合うゲームを行う。

先手は初手、数列の先頭から1つか2つの要素をとる。
後手は数列の末尾から、先手のとった要素数またはそれに+1した数の要素をとる。

以後、先手は数列の先頭、後手は末尾から、相手が直前とった要素数またはそれに+1した要素をとることを繰り返す。
そのようなとり方ができなくなった場合ゲームは終了する。
それぞれ、とった要素の総和が自分のスコアとなる。
先手は(先手のスコア-後手のスコア)を最大化しようとし、後手は最小化しようとする場合、最終的なスコア差はいくつとなるか。

解法

状態として考えられるのは以下のとおりである。
f(先手がとった総要素数, 後手がとった総要素数, 手番, 相手が直前にとった要素数) := 以降のスコア差の最大値/最小値

この状態であるが、O(N^2.5)程度であり、Nが最大の4000でも600万程度しか状態がない。
よってうまくこれらの状態をメモリ上に格納すれば、単に総当たりするだけで解ける。
下手なメモリ配置をするとMLEするので注意。

以下のコードでは、先に両者のとった総要素数に対し、そのような状態に至る「相手が直前にとった要素数」の範囲を求め、状態をvectorに詰め込んでいる。

int N;
int A[4040];
ll S[4040];

int LL[4040][4040][2];
int RR[4040][4040][2];

vector<ll> memo;

void check(int L,int R,int K,int t) {
	if(R-L+1<K) return;
	if(LL[L][R][t]<=K && K<=RR[L][R][t]) return;
	LL[L][R][t]=min(LL[L][R][t],K);
	RR[L][R][t]=max(RR[L][R][t],K);
	if(t==0) {
		check(L+K,R,K,1);
		check(L+K+1,R,K+1,1);
	}
	else {
		check(L,R-K,K,0);
		check(L,R-(K+1),K+1,0);
	}
	
}

ll turn(int L,int R,int K,int t) {
	if(R-L+1<K) return 0;
	ll& ret=memo[(K-LL[L][R][t])+RR[L][R][t]];
	if(ret>-1LL<<60) return ret;
	
	if(t==0) {
		ret = turn(L+K,R,K,1) + S[L+K]-S[L];
		if(L+K+1<=R+1) ret = max(ret, turn(L+K+1,R,K+1,1) + S[L+K+1]-S[L]);
	}
	else {
		ret = turn(L,R-K,K,0) - (S[R+1]-S[R-K+1]);
		if(R-(K+1)>=L-1) ret = min(ret, turn(L,R-(K+1),K+1,0) - (S[R+1]-S[R-(K+1)+1]));
	}
	return ret;

}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N) {
		cin>>A[i];
		S[i+1]=S[i]+A[i];
	}
	FOR(x,N) FOR(y,N) LL[x][y][0]=LL[x][y][1]=N+1;
	FOR(x,N) FOR(y,N) RR[x][y][0]=RR[x][y][1]=-1;
	check(0,N-1,1,0);
	
	int tot=0;
	FOR(x,N) FOR(y,N) FOR(i,2) {
		if(LL[x][y][i]<=RR[x][y][i]) {
			int num = RR[x][y][i]-LL[x][y][i]+1;
			RR[x][y][i]=tot;
			tot+=num;
		}
	}
	
	memo.resize(tot+5);
	FORR(r,memo) r=-1LL<<60;
	
	cout<<turn(0,N-1,1,0)<<endl;
}

まとめ

これはどういう問題なんだろう。状態を総当たりすればできてしまうので、いかにメモリに状態を乗せきるか?