洛谷2599 【ZJOI2009】取石子游戏(博弈论+DP)

传送门

【题目分析】

这谁想得到要DP啊。。。。。ZJOI果然神题倍出。

参考了YYB的博客,传送门。(确实讲的很好!一看就懂!)

定义两个数组:L[i][j]R[i][j],L[i][j]表示在区间[i,j]左边放一堆数量为L[i][j]的石子,此时先手必败,R[i][j]表示在区间[i,j]右侧放一堆数量为R[i][j]的石子,此时先手必败。

如果存在两个或以上的L[i][j],那么显然左边的可以通过取任意个石子相互转化,就形成了必败局势到必败局势的转移,非法。而如果不存在L[i][j],即无论左边有多少个石子先手都是必胜,所以一旦拿最左边一堆的石子,局势仍然为必胜态,先手必输,所以只会取最右边的石子,而不管怎么取,场面仍然为先手必败态,所以又出现了必败到必败的转移,非法,所以可以保证L和R都拥有唯一的解。

所以求出两个数组后只用判断a[1]是否与L[2][n]相等即可。

所以大力讨论一波,假设我们已经求出所有区间长度为len-1的L和R值,现在考虑如何去求区间长度为len的L,R。

为了简便叙述,假设我们现在要求的区间为[i,j],令l=L[i][j-1],r=R[i][j-1],x=a[j]。

{\color{Red} 1.}{\color{Red} x=r}

这种比较简单,因为当前堆已经可以使整个局面进入必败态,所以L[i][j]=0。

因为我们对R的定义就是当前局面下先手必败, 所以左边不需要放石子。

{\color{Blue} 2.x<l,x<r}

这种情况下L[i][j]=x。

令先手选择一个堆取走后该堆石子剩z个:

因为z一定还是小于l和r的,所以只要后手模仿先手策略将另一堆也取到z,那么就又回到了这个情况,最后一定是由先手率先取完某一堆。大力枚举:

{\color{Blue} (1)}先手将最左侧的一堆取完。

考虑到我们对R的定义,所以现在因为有x

{\color{Blue}(2)}先手将最右侧一堆取完。

同理,当前局势就是在[i,j-1]左侧有l个石子的情况下取成当前局势,后手仍然必胜。

所以当L[i][j]=x的时候,先手必败,又因为有唯一性,所以L[i][j]就是x。

{\color{Green} 3.r<x<l}

这种情况L[i][j]=x-1。

令先手选择一个堆取走后该堆石子剩z个。

{\color{Green} (1)}先手取的是最左侧一堆,且z

这时候只用将右侧取成z,场面就变为情况2了,此时先手必败。

{\color{Green} (2)}先手取的是最左侧一堆,且z\geq r

这时候将右侧取为z+1,就又回到情况3递归处理。

{\color{Green} (3)}先手取的是最右侧一堆,且z

此时将最左侧取为z,回到情况2。

{\color{Green} (4)}先手取的是最左侧一堆,且z=r。

此时直接将最左边取完,那么当前局面就是r的定义,先手必败。

{\color{Green} (5)}先手取的是最左侧一堆,且z>r。

将左侧取为z-1,回到情况3递归处理。

所以综上,当L[i][j]=x-1时,先手必败。

{\color{DarkBlue} 4.l<x<r}

跟情况3类似,不再赘述。

{\color{DarkGreen} 5.x>l,x>r}

这种情况L[i][j]=x。

令先手选择一个堆取走后该堆石子剩z个。

{\color{DarkGreen} (1)z>l,z>r}

模仿先手策略,将另一堆取成z个,回到情况5递归处理。

(不知道为啥LATEX炸了)

(2) z < l, z < r

按照情况1,直接将另一堆取成z个,就变成情况1。

(3) otherwise

按照情况3和4进行取石子即可。

R的求法与L的求法对称,这里应该不用证明了。

直接上代码吧。

【代码~】

#include
using namespace std;
const int MAXN=1e3+10;

int n;
int a[MAXN];
int L[MAXN][MAXN],R[MAXN][MAXN];

int Read(){
	int i=0,f=1;
	char c;
	for(c=getchar();(c>'9'||c<'0')&&c!='-';c=getchar());
	if(c=='-')
	  f=-1,c=getchar();
	for(;c>='0'&&c<='9';c=getchar())
	  i=(i<<3)+(i<<1)+c-'0';
	return i*f;
}

int main(){
	int T=Read();
	while(T--){
		n=Read();
		for(int i=1;i<=n;++i){
			a[i]=Read();
			L[i][i]=R[i][i]=a[i];
		}
		for(int i=2;i<=n;++i){
			for(int j=1,k=j+i-1;k<=n;++j,++k){
				int l=L[j][k-1],r=R[j][k-1];
				int x=a[k];
				if(x==r)
				  L[j][k]=0;
				else{
					if(((xl&&x>r)))
					  L[j][k]=x;
					if(x>r&&xl&&xl&&x>r))
					  R[j][k]=x;
					if(xl)
					  R[j][k]=x-1;
					if(xr)
					  R[j][k]=x+1;
				}
			}
		}
		if(a[1]==L[2][n])
		  puts("0");
		else
		  puts("1");
	}
	return 0;
}

 

你可能感兴趣的:(————DP————)