题面
题意
从左到右有几堆石子,双方轮流取石子,每次取时只能从最左边或者是最右边的堆中取任意个石子,不能操作的人算输,问先手有无必胜策略。
做法
首先定义dp状态:
表示在第
堆石子到第
堆石子左边放
个石子后是必败态。
表示在第
堆石子到第
堆石子左边放
个石子后是必败态。
两个值都可以为0。
不难发现这两个值存在且唯一,最后只要判断
是否等于
即可。
下面考虑
的状态转移(
与它的转移方式相同):
递归边界:当
时,很显然,
首先求出
若
,则
然后可以发现,后手可以尽量复制先手的操作,先手在左边取
个后,后手可以在右边也同样取
个,这样可以如果
,就可以保证先手先取完某一堆,这样只要保证先手取完某一堆后不是必败态就行,也就说如果先手取完第
堆,则要保证此时第
堆剩余的石子数不等于R,如果先手取完第
堆,则要保证此时第
堆剩余的石子数不等于L,因此还要加几个判断:
1.若
,则当先手在第
堆取了
个时,后手不能在第
堆取
个(否则先手可以直接取完第
堆的所有石子),因此
,这样当先手在左边取了
个或更多个石子后,后手只要在右边取
个石子即可。
2.若
,则当先手在第
堆取了
个时,后手同样不能复制先手的操作,因此
,这样当先手在右边取了
个或更多个石子后,后手只要在左边取
个石子即可。
3.若
,则
仍为
,理由与上面相同。
代码
#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));
}
}