[ZJOI2009]取石子游戏

题面

题意

从左到右有几堆石子,双方轮流取石子,每次取时只能从最左边或者是最右边的堆中取任意个石子,不能操做的人算输,问先手有无必胜策略。html

作法

首先定义dp状态:
l e f t [ i ] [ j ] left[i][j] 表示在第 i i 堆石子到第 j j 堆石子左边放 l e f t [ i ] [ j ] left[i][j] 个石子后是必败态。
r i g h t [ i ] [ j ] right[i][j] 表示在第 i i 堆石子到第 j j 堆石子左边放 r i g h t [ i ] [ j ] right[i][j] 个石子后是必败态。
两个值均可觉得0。
不难发现这两个值存在且惟一,最后只要判断 l e f t [ 2 ] [ n ] left[2][n] 是否等于 n u m [ 1 ] num[1] 便可。
下面考虑 l e f t [ i ] [ j ] left[i][j] 的状态转移( r i g h t [ i ] [ j ] right[i][j] 与它的转移方式相同):
递归边界:当 i = j i=j 时,很显然, l e f t [ i ] [ j ] = r i g h t [ i ] [ j ] = n u m [ i ] left[i][j]=right[i][j]=num[i]
首先求出 L = l e f t [ i ] [ j 1 ] , R = r i g h t [ i ] [ j 1 ] L=left[i][j-1],R=right[i][j-1]
R = n u m [ v ] R=num[v] ,则 l e f t [ i ] [ j ] = 0 left[i][j]=0
而后能够发现,后手能够尽可能复制先手的操做,先手在左边取 x x 个后,后手能够在右边也一样取 x x 个,这样能够若是 l e f t [ i ] [ j ] = n u m [ j ] left[i][j]=num[j] ,就能够保证先手先取完某一堆,这样只要保证先手取完某一堆后不是必败态就行,也就说若是先手取完第 i i 堆,则要保证此时第 j j 堆剩余的石子数不等于R,若是先手取完第 j j 堆,则要保证此时第 i i 堆剩余的石子数不等于L,所以还要加几个判断:
1.若 R < n u m [ v ] < L R<num[v]<L ,则当先手在第 i i 堆取了 n u m [ v ] R num[v]-R 个时,后手不能在第 j j 堆取 n u m [ v ] R num[v]-R 个(不然先手能够直接取完第 i i 堆的全部石子),所以 l e f t [ i ] [ j ] = n u m [ v ] 1 left[i][j]=num[v]-1 ,这样当先手在左边取了 n u m [ v ] R num[v]-R 个或更多个石子后,后手只要在右边取 n u m [ v ] R + 1 num[v]-R+1 个石子便可。
2.若 L < n u m [ v ] < R L<num[v]<R ,则当先手在第 j j 堆取了 n u m [ v ] L num[v]-L 个时,后手一样不能复制先手的操做,所以 l e f t [ i ] [ j ] = n u m [ v ] + 1 left[i][j]=num[v]+1 ,这样当先手在右边取了 n u m [ v ] L num[v]-L 个或更多个石子后,后手只要在左边取 n u m [ v ] R + 1 num[v]-R+1 个石子便可。
3.若 n u m [ v ] > max ( L , R ) num[v]>\max(L,R) ,则 l e f t [ i ] [ j ] left[i][j] 仍为 n u m [ v ] num[v] ,理由与上面相同。ios

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1010
using namespace std;

int T,n,num[N],le[N][N],ri[N][N];

int askl(int u,int v);
int askr(int u,int v)
{
	if(u==v) return num[u];
	if(ri[u][v]!=-1) return ri[u][v];
	int L,R,res;
	L=askl(u+1,v);
	R=askr(u+1,v);
	if(num[u]==L) res=0;
	else res=num[u]-(num[u]>L)+(num[u]>=R);
	return ri[u][v]=res;
}

int askl(int u,int v)
{
	if(u==v) return num[u];
	if(le[u][v]!=-1) return le[u][v];
	int L,R,res;
	L=askl(u,v-1);
	R=askr(u,v-1);
	if(num[v]==R) res=0;
	else res=num[v]-(num[v]>R)+(num[v]>=L);
	return le[u][v]=res;
}

int main()
{
	int i,j;
	cin>>T;
	while(T--)
	{
		memset(le,-1,sizeof(le));
		memset(ri,-1,sizeof(ri));
		scanf("%d",&n);
		for(i=1;i<=n;i++)
		{
			scanf("%d",&num[i]);
		}
		printf("%d\n",num[1]!=askl(2,n));
	}
}