[ZJOI2009]取石子游戏

190 篇文章 2 订阅
84 篇文章 2 订阅

题面

题意

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

做法

首先定义dp状态:
l e f t [ i ] [ j ] left[i][j] left[i][j]表示在第 i i i堆石子到第 j j j堆石子左边放 l e f t [ i ] [ j ] left[i][j] left[i][j]个石子后是必败态。
r i g h t [ i ] [ j ] right[i][j] right[i][j]表示在第 i i i堆石子到第 j j j堆石子左边放 r i g h t [ i ] [ j ] right[i][j] right[i][j]个石子后是必败态。
两个值都可以为0。
不难发现这两个值存在且唯一,最后只要判断 l e f t [ 2 ] [ n ] left[2][n] left[2][n]是否等于 n u m [ 1 ] num[1] num[1]即可。
下面考虑 l e f t [ i ] [ j ] left[i][j] left[i][j]的状态转移( r i g h t [ i ] [ j ] right[i][j] right[i][j]与它的转移方式相同):
递归边界:当 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] 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] L=left[i][j1],R=right[i][j1]
R = n u m [ v ] R=num[v] R=num[v],则 l e f t [ i ] [ j ] = 0 left[i][j]=0 left[i][j]=0
然后可以发现,后手可以尽量复制先手的操作,先手在左边取 x x x个后,后手可以在右边也同样取 x x x个,这样可以如果 l e f t [ i ] [ j ] = n u m [ j ] left[i][j]=num[j] left[i][j]=num[j],就可以保证先手先取完某一堆,这样只要保证先手取完某一堆后不是必败态就行,也就说如果先手取完第 i i i堆,则要保证此时第 j j j堆剩余的石子数不等于R,如果先手取完第 j j j堆,则要保证此时第 i i i堆剩余的石子数不等于L,因此还要加几个判断:
1.若 R &lt; n u m [ v ] &lt; L R&lt;num[v]&lt;L R<num[v]<L,则当先手在第 i i i堆取了 n u m [ v ] − R num[v]-R num[v]R个时,后手不能在第 j j j堆取 n u m [ v ] − R num[v]-R num[v]R个(否则先手可以直接取完第 i i i堆的所有石子),因此 l e f t [ i ] [ j ] = n u m [ v ] − 1 left[i][j]=num[v]-1 left[i][j]=num[v]1,这样当先手在左边取了 n u m [ v ] − R num[v]-R num[v]R个或更多个石子后,后手只要在右边取 n u m [ v ] − R + 1 num[v]-R+1 num[v]R+1个石子即可。
2.若 L &lt; n u m [ v ] &lt; R L&lt;num[v]&lt;R L<num[v]<R,则当先手在第 j j j堆取了 n u m [ v ] − L num[v]-L num[v]L个时,后手同样不能复制先手的操作,因此 l e f t [ i ] [ j ] = n u m [ v ] + 1 left[i][j]=num[v]+1 left[i][j]=num[v]+1,这样当先手在右边取了 n u m [ v ] − L num[v]-L num[v]L个或更多个石子后,后手只要在左边取 n u m [ v ] − R + 1 num[v]-R+1 num[v]R+1个石子即可。
3.若 n u m [ v ] &gt; max ⁡ ( L , R ) num[v]&gt;\max(L,R) num[v]>max(L,R),则 l e f t [ i ] [ j ] left[i][j] left[i][j]仍为 n u m [ v ] num[v] num[v],理由与上面相同。

代码

#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));
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值