题解——P3185 [HNOI2007]分裂游戏

本题是一道较好的 SG 函数练习题,理解其推导过程可以帮助我们更好的掌握 SG 函数。

1.题意简述

给定一串长度为 \(n\) 的序列,第 \(i\) 位的数值为 \(a[i]\),每次操作可以选定三个下标 \(i,j,k\) 满足 \(i < j \le k\),使 \(a[i]-1,a[j]+1,a[k]+1\),两人轮流操作,无法操作者为输。求先手必胜的第一步的走法以及第一步走法的种类数。

2.解题思路

显然,本题是一道博弈论 (废话),通过分析,我们可以得到以下结论:

  • 最终无法操作的局面就是只有第 \(n\) 位有数值,其余位都是 \(0\)。

  • 对于 $\forall i \in [1,n] ,a[i] \ge 2 $,我们都可以将其模 \(2\) 后再处理。(因为后手可以模仿先手的操作,所以一定可以同时减少 \(2\) 的倍数)

  • 面向数据编程(雾),本题数据范围为 \(N \le 21\),所以只要复杂度不是阶乘级就可以接受。我们以每一个豆子为子游戏,暴力枚举 \(i,j,k\) 求出每一位上只有一个豆子时的 SG 函数值。这是本代码的时间复杂度瓶颈,为 \(\Theta(n^3)\)。

3.代码

#include <iostream>
#include <cstdio>
#include <cctype>
#include <cstring>
#define il inline
#define ll long long
#define re register
#define gc getchar
using namespace std;
il int read() {
	int x=0;bool f=0;char ch=gc();
	while(!isdigit(ch)) {f|=ch=='-';ch=gc();}
	while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
	return f?-x:x;
}	

il int max(int a,int b) {return a>b?a:b;}

il int min(int a,int b) {return a<b?a:b;}

const int N=100;
int T,n,ans,x,y,z,sg;
int a[N],SG[N];
bool vis[N<<1];//异或可能会超出n的范围,为了防止爆数组,要开两倍空间 

signed main() {
	T=read();
	while(T--) {
		ans=x=y=z=sg=0;
		memset(SG,0,sizeof(SG));
		n=read();
		for(re int i=1;i<=n;++i) a[i]=read();
		for(re int i=n-1;i;--i) {//因为SG值由其后继状态得到,所以从后往前推 
			memset(vis,0,sizeof(vis));
			for(re int j=i+1;j<=n;++j) 
				for(re int k=j;k<=n;++k) 
					vis[SG[j]^SG[k]]=1;//SG函数定义 :SG[i]=mex(其后继状态的SG值)
			for(re int j=0;;++j) if(!vis[j]) {SG[i]=j;break;}
		}	
		for(re int i=1;i<=n;++i) if(a[i]&1) sg^=SG[i];//只有模2余1的位草堆答案有贡献 
		for(re int i=1;i<=n;++i) 
			if(a[i]) //这一位有数才可以移动 
			for(re int j=i+1;j<=n;++j) 
				for(re int k=j;k<=n;++k) 
					if((sg^SG[i]^SG[j]^SG[k])==0)	{
						//对于总SG值不为0的博弈,第一步将总SG值变为0就是必胜策略
						//移动第i位,对总SG值少了SG[i],多了SG[j],SG[k]
						if(!ans) {x=i,y=j,z=k;}
						++ans;
					}	
		printf("%d %d %d\n",x-1,y-1,z-1);//题目中瓶子编号为0~n-1,所以最后ans-1 
		printf("%d\n",ans);
	}	
	return 0;
}	
上一篇:学习知识点链接


下一篇:【BZOJ2410】Nim游戏(博弈论)