树上阶梯nim

题面

在某个不知名的行星上蕴含着大量冰晶矿,Jim和他的好兄弟Swan自然不能放过这个赚钱的好机会。Jim在整个星球上开掘树型矿洞,每个矿坑之间都有矿道相连。Jim和Swan在每个矿坑开采了大量的矿石,现在他们面临一个新的问题,怎么把所有的矿石运出去。
已知,矿坑与矿坑之间形成了有向的树形结构,即除0号矿坑以外每个矿坑都有与其相连的父亲矿坑。Jim总共开采了 个矿坑并将其从0到 n-1 编号 ,每个矿坑都存有 val[i] 个单位的矿石。Jim和Swan每次操作都可以从某个矿坑移动至少1个单位的矿石到其父亲矿坑。Jim和Swan决定比试一下,由Jim开始轮流操作,最后不能操作的人输。Jim偷偷的找到了你,他想知道在两人都采取最优策略的情况下是否Jim能够赢得这场比试。
这个问题,可以简称它为:树上阶梯nim裸题,复杂度O(n)。
阶梯nim:有n个位置1…n,每个位置上有ai个石子。有两个人轮流操作。
操作步骤是:挑选1…n中任意一个存在石子的位置i,将至少1个石子移动至i−1位置(也就是最后所有石子都堆在在0这个位置)。谁不能操作谁输。求先手必胜还是必败。
相信大家都会做,就是把所有的奇数位置上的石子数量异或一下,若异或和为0,则先手输,否则后手输。现在在树上做的话,我们需要考虑一下阶梯nim这样做的原理是什么。
唉,感觉如果要考虑原理的话好难写,要写好多话,这里就直接给出一个结论吧。
(由于题目中需要把所有矿石移动到节点0,所以,设节点0的深度为0)那么结论就是:先把所有深度为1,3,5…的val[i]异或起来,记录在sum[i]中(sum[1]=所有深度为1的节点的val异或和,sum[3]为所有深度为3的节点的val异或和),然后把sum[1],sum[3],sum[5]…异或起来,记录为ans。此时,ans为0,先手必败,否则先手必胜。
当清楚了解了阶梯nim的原理后,树上阶梯nim应该很好理解了,自己猜一波结论应该也可以猜出来了。(相当于做了一个二维的阶梯nim)

代码:

#include 
using namespace std;
const int N=2e5+5;
int T,n,u,maxn,ans;
int a[N],d[N],sum[N];
int cnt,head[N];
struct edge{
     int next,to;}e[N<<1];

inline void add(int u,int v)
{
     
	cnt++;
	e[cnt].next=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}

void dfs(int u)
{
     
	for (register int i=head[u]; ~i; i=e[i].next)
	{
     
		d[e[i].to]=d[u]+1;
		dfs(e[i].to);
	}
}

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
inline int read()
{
     
    int ret=0,ff=1; char ch=getchar();
    while(!isdigit(ch)){
     if(ch=='-') ff=-ff;ch=getchar();}
    while(isdigit(ch)){
     ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();}
    return ret*ff;
}
inline void write(int x)
{
     
    if(x<0){
     x=-x;putchar('-');}
    if(x>9) write(x/10);
    putchar(x%10+48);
}

int main(){
     
	T=read();
	while (T--)
	{
     
		cnt=0; memset(head,-1,sizeof(head));
		n=read();
		for (register int i=1; i<n; ++i) 
		{
     
			u=read();
			add(u,i);
		}
		for (register int i=0; i<n; ++i) a[i]=read();
		memset(d,0,sizeof(d));
		d[0]=0;
		dfs(0);
		memset(sum,0,sizeof(sum));
		for (register int i=1; i<n; ++i) sum[d[i]]^=a[i];
		maxn=0;
		for (register int i=1; i<n; ++i) maxn=max(maxn,d[i]);
		ans=0;
		for (register int i=1; i<=maxn; i+=2) ans^=sum[i];
		if (ans) {
     putchar('w'); putchar('i'); putchar('n');}
		else {
     putchar('l'); putchar('o'); putchar('s'); putchar('e');}
		putchar('\n');
	}
return 0;
}

你可能感兴趣的:(博弈论)