AOJ 1353: Sweet War

dp[i][j][k]: i番目のお菓子でkさんが始めてから先手の人がj得て終わるためには(先手の人のエネルギー)-(後手の人のエネルギー)が何以上/以下であればよいか

見てのとおり遷移が複雑です。次の2通りの遷移があります。

  • i番目をkさんは選ぶ。相手のdpテーブルから先手の人がj得て終わる状態にできない(相手が余分に取ってしまうとかで)ような手を起こさないためにはどのくらいエネルギーが必要かを計算できます。
  • i番目をkさんじゃないほうに取らせてi+1番目で自分の番をやる(↑より遷移が楽ですが、自分のほうがエネルギーが多い状況にしないといけません。また、1だけパスする分のエネルギーを消費します。)

こういう複雑な深い思考を要する問題は苦手。

#include<stdio.h>
#include<algorithm>
using namespace std;
int r[200];
int s[200];
long long dp[200][200][2];
int v[200][200][2];
int n;
long long INF=99999999999999LL;
int sum;
long long solve(int a,int b,int c){
	if(v[a][b][c])return dp[a][b][c];
	v[a][b][c]=1;
	if(a==n){
		if(b==0){
			if(c==0)dp[a][b][c]=-INF;
			else dp[a][b][c]=INF;
		}else{
			if(c==0)dp[a][b][c]=INF;
			else dp[a][b][c]=-INF;
		}
	//	printf("%d %d %d: %lld\n",a,b,c,dp[a][b][c]);
		return dp[a][b][c];
	}
	if(c==0)dp[a][b][c]=INF;
	else dp[a][b][c]=-INF;
	if(b-s[a]*(1-c)>=0){
		//long long t=solve(a+1,b-s[a]*(1-c),!c);
		if(c==0){
			long long t=-INF;
		//	printf("%d\n",b-s[a]);
			for(int i=0;i<b-s[a];i++){
				//if(i>sum)printf("ng\n");
				t=max(t,solve(a+1,i,1)+1);
			}
			dp[a][b][c]=min(dp[a][b][c],t-r[a]);
		}else{
			long long t=INF;
			for(int i=b+1;i<=sum;i++)t=min(t,solve(a+1,i,0)-1);
			dp[a][b][c]=max(dp[a][b][c],t+r[a]);
		}
	}
	if(b-s[a]*c>=0){
		long long t=solve(a+1,b-s[a]*c,c);
		if(c==0){
			t=max(t+r[a],0LL);
			dp[a][b][c]=min(dp[a][b][c],t+1);
		}else{
			t=min(t-r[a],0LL);
			dp[a][b][c]=max(dp[a][b][c],t-1);
		}
	}
//	printf("%d %d %d: %lld\n",a,b,c,dp[a][b][c]);
	return dp[a][b][c];
}
int main(){
	int a,b,c;
	scanf("%d%d%d",&a,&b,&c);
	n=a;
	for(int i=0;i<a;i++)scanf("%d%d",r+i,s+i);
	sum=0;
	for(int i=0;i<a;i++)sum+=s[i];
	for(int i=sum;i>=0;i--){
		if(b-c>=solve(0,i,0)){
			printf("%d %d\n",i,sum-i);return 0;
		}
	}
	
}