码迷,mamicode.com
首页 > 其他好文 > 详细

P2599 [ZJOI2009]取石子游戏

时间:2019-11-09 15:26:22      阅读:105      评论:0      收藏:0      [点我收藏+]

标签:16px   href   输入格式   就是   namespace   定义   algorithm   algo   初始   

题目描述

在研究过Nim游戏及各种变种之后,Orez又发现了一种全新的取石子游戏,这个游戏是这样的: 有n堆石子,将这n堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。 Orez问:对于任意给出一个初始一个局面,是否存在先手必胜策略。

输入格式

文件的第一行为一个整数T,表示有 T组测试数据。对于每组测试数据,第一行为一个整数n,表示有n堆石子;第二行为n个整数ai,依次表示每堆石子的数目。

输出格式

对于每组测试数据仅输出一个整数0或1。其中1表示有先手必胜策略,0表示没有。

输入输出样例

输入 #1
1

4

3 1 9 4
输出 #1
0

说明/提示

数据范围

对于30%的数据 n≤5 ai≤10^5

对于100%的数据 T≤10 n≤1000 每堆的石子数目≤10^9

思路

By  :yybyyb

发现SG函数等东西完全找不到规律,无奈只能翻题解。

首先设L[i][j]表示在[i,j]这一段区间的左侧放上一堆数量为L[i][j]的石子后,先手必败。同理定义R[i][j]表示右侧。

首先我们可以证明L[i][j]唯一,假设存在两个L[i][j],显然较大的那个可以通过一步转移转移到较小的那个,所以不合法。因此L[i][j]唯一。

接下来考虑如何证明L[i][j]一定存在。假设L[i][j]不存在,那么对于这段区间而言,在左边加上任意一堆石子先手都必胜,既然先手必胜意味着先手进行一步操作之后可以到达一个必败态,这里分情况讨论。假设先手拿的是最左边的一堆石子,因为不存在L[i][j],所以只要拿了左边的石子之后,当前局面都是必胜态,所以不可能拿左边的石子。那么只能拿右边的石子,那么无论右边拿了一定量之后,无论左边添加了多少,都是一个必败态,那么此时后手在左侧随便拿走一定数量,这个状态也还是一个必败态,显然也不成立。因此L[i][j]必定存在。

综上,我们知道了L[i][j]一定存在并且唯一,而L[][],R[][]显然是对称的,因此R[i][j]也满足上述性质。

现在考虑如何求解L[i][j],R[i][j]同理。首先边界情况显然,L[i][i]=a[i],因为只剩下两堆一模一样的情况的时候,后手只需要模仿先手的行动对称执行就好了,这样子一定不会输,即先手必败。

接下来来大力分类讨论,为了方便,设L=L[i][j−1],R=R[i][j−1],x=a[j]

  • x=R

    这种情况下显然只需要直接把a[j]放进去就好了,即这个区间本身就是一个必败态。所以L[i][j]=0。

  • x<L,x<R

    这种情况下L[i][j]=x。这种情况下最靠左的L[i][j]和x=a[j]是相同的,意味着先手无论怎么取,后手显然可以学着它的方法取,也就意味着左右两堆中显然必然会先拿完一堆,此时后手学着拿的那一堆的石子数一定也是小于L,R的。假设先手先拿完了最靠右的一堆,即剩下了[i,j−1],因为L[i][j−1]表示的是在这一段区间最左侧加入一个L[i][j−1的堆,无论先手怎么取先手都是必败的,那么我们等价的认为先手取走了这一堆的一部分,显然后手是必胜的。假如先手先取完的是最左的一堆,同理,R[i][j−1]的含义是在最右侧加入了一堆,而a[j]<R[i][j−1],我们还是可以等价的认为先手在这一堆中取走了若干石子,而这个状态对于先手而言是必败状态,因此显然后手必胜。

  • R<x<L

    这种情况下L[i][j]=x−1。这样子考虑,假设先手先拿了左边这一堆,那么假设还剩下了z个石子,如果z<R,后手把右侧的那一堆也给拿成z就变成了上面的情况。如果z≥R,那么后手把最后那一堆拿成z+1,于是又回到了这种情况,相当于这种情况递归处理。如果先手先拿的是右侧的这一堆,还是一样的,假设把它拿成了z,如果z<R,同上可以变成x<L,x<R的情况;如果y=R,直接把左边拿完,就变成了R[i][j−1]的定义了,先手必败;如果z>R,把左边那堆变成z−1,同样递归处理。

  • L<x<R

    分析同上,L[i][j]=x+1。

  • x>L,x>R

    L[i][j]=x。还是一样的,假设先手把其中一堆拿成了z。如果z>L,R,跟着先手拿成一样多的石子则又回到了这种情况。如果z<L,R,则可以回到情况x<L,R。否则的话对应着把另外一堆变成z+1或者z−1,对应着L<x<R和R<x<L两种情况。

而R[][]和L[][]是对称的,类似的求解即可。

那么最终只需要判断L[2][n]和a[1]是否相等即可判断胜负情况。

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;

int t,n,a[N];
int l[N][N],r[N][N];

int main () {
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		for(int i=1; i<=n; i++) {
			scanf("%d",&a[i]);
			l[i][i]=r[i][i]=a[i];
		}
		for(int len=2; len<=n; len++)
			for(int i=1,j=i+len-1; j<=n; i++,j++) {
				int x=a[j],ll=l[i][j-1],rr=r[i][j-1];
				if(x==rr)
					l[i][j]=0;
				else if((x>rr&&x>ll)||(x<ll&&x<rr))
					l[i][j]=x;
				else if(rr<x&&x<ll)
					l[i][j]=x-1;
				else
					l[i][j]=x+1;
				x=a[i],ll=l[i+1][j],rr=r[i+1][j];
				if(x==ll)
					r[i][j]=0;
				else if((x>rr&&x>ll)||(x<ll&&x<rr))
					r[i][j]=x;
				else if(rr<x&&x<ll)
					r[i][j]=x+1;
				else
					r[i][j]=x-1;
			}
		if(a[1]==l[2][n])
			printf("0\n");
		else
			printf("1\n");
	}
	return 0;
}

 

P2599 [ZJOI2009]取石子游戏

标签:16px   href   输入格式   就是   namespace   定义   algorithm   algo   初始   

原文地址:https://www.cnblogs.com/mysh/p/11826050.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!