[HNOI2014]江南乐

题目

好像是个\(multi\)博弈的基本问题呢

\(multi\)博弈就是在原来的\(Nim\)游戏的规则上变成了一次可以把一堆石头分裂成更多堆石头

而且博弈论中的一个重要定理,可以把很多博弈都转化成\(Nim\)游戏的

在我们每次只能进行一次操作的时候,对于多个游戏的和,我们可以把单个游戏抽象成一堆个数为这个游戏的\(SG\)函数的值的石子,从而转化成\(Nim\)游戏

也就是说这道题我们直接对每一堆石子\(x\)求一下\(SG(x)\),最后取一个异或就好了

至于证明,不会证,告辞,看论文去吧

现在的问题变成了如何求\(SG(x)\)

首先如果\(x<F\),上来就是必败状态,那么显然\(SG(x)=0\)

如果\(x>=F\),那么我们发现这个状态的后继状态是多个状态的组合,也就是多个游戏的和,这样我们就需要取异或来合并这些状态,最后在按照\(SG\)函数的规则取一个\(mex\)

考虑直接暴力枚举当前的状态\(x\)分成了\(i\)组,那么有\(x\%i\)组都是\(\left \lfloor \frac{x}{i} \right \rfloor\)个,有\(i-x\%i\)组是\(\left \lfloor \frac{x}{i} \right \rfloor+1\)

于是我们就可以写一个暴力啦

但是这样转移复杂度是\(O(n^2)\)的,显然不行

考虑到那个\(\left \lfloor \frac{x}{i} \right \rfloor\)一共有根号中取值,所以我们可以直接整除分块,之后根据\(x\%i\)\(i-x\%i\)的奇偶性大力讨论一波就好好啦

代码

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int sg[100005],vis[100005];
std::vector<int> v[100005];
int T,F,n;
int dfs(int x) {
	if(vis[x]) return sg[x];
	vis[x]=1;
	for(re int l=2,r;l<=x;l=r+1) {
		r=x/(x/l);int t=x/l;
		if(l==r) {
			int y=0,res=x%l;
			if(res&1) y^=dfs(t+1);
			if((l-res)&1) y^=dfs(t);
			v[x].push_back(y);
			continue;
		}
		if(x&1) {
			v[x].push_back(dfs(t)^dfs(t+1));
			if(t&1) v[x].push_back(dfs(t));
		}
		else {
			v[x].push_back(0);
			if(t&1) v[x].push_back(dfs(t+1));
		}
	}
	std::sort(v[x].begin(),v[x].end());
	v[x].erase(std::unique(v[x].begin(),v[x].end()),v[x].end());
	for(re int i=0;i<v[x].size();i++) 
		if(i!=v[x][i]) {sg[x]=i;return sg[x];}
	return sg[x]=v[x].size();
}
int main() {
	T=read();F=read();
	for(re int i=0;i<F;i++) vis[i]=1;
	while(T--) {
		n=read();int ans=0;
		for(re int x,i=1;i<=n;i++) {
			x=read();
			ans^=dfs(x);
		}
		putchar(ans?'1':'0');putchar(' ');
	}
	return 0;
}
posted @ 2019-04-21 16:23  asuldb  阅读(155)  评论(0编辑  收藏  举报