[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;
}