题目描述
你在和蒟蒻wyz玩树上NIM游戏。
游戏规则是这样的,有一棵n个节点的有根树,节点编号为0到n-1,根为0号节点。游戏开始时,树上的每个节点都可能有一些石子。两位玩家轮流操作,每次操作玩家可以选择一个节点,并把该节点上的一些石子(个数不能为0)拿到它的父亲节点上去。如果轮到某位玩家时,该玩家没有任何合法的操作可以执行,则判负。
你想知道当前局面先手能否必胜。
输入格式
本题有多组数据,第一行为一个非负整数T,表示数据组数。
对于每组数据,第一行一个整数n,表示节点数目。
接下来一行为n-1个整数fa[1]..fa[n-1],分别描述了除根节点外每个点的父亲。方便起见,保证0<=fa[i]<i。
接下来一行为n个非负整数a[0]..a[n-1],分别描述了每个点初始的石子数。保证0<=a[i]<134217728。
保证1<=T<=100,1<=n<=300000,且保证sigma(n)<=650000。
输出格式
对于每组数据,输出一行,若先手必胜则输出"win",否则输出"lose"(不含引号)。
样例
【样例输入】
2
2
0
1000 1
4
0 1 0
2 3 3 3
【样例输出】
win
lose
一些想法
考虑一条链时的情况:
三个点:0 -> 1 -> 2。
根节点无法操作不考虑
简单计算 SG 函数发现 SG 函数与 2 上的石子数量无关
更长的链:
0 - >1 -> 2 -> 3 -> 4……
SG 函数与 0,2,4 等偶数节点的石子数量无关。
链上必胜策略:
如果所有奇数点的石子个数是 0,那么此时先手必败。
假设先手将 p 的 x 个石子拿到父亲节点,那么后手可以把被拿到 p - 1 的这 x 个石子再拿到 x - 2 使得奇数点的石子个数依然是 0。
那么此时奇数点就形成了一个 NIM 游戏。
推广到树上:
DFS 求出每个节点的深度,对所有奇数深度的节点的石子数量求亦或和,若不为 0,则先手必胜。
代码具体解析看注释
AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=300000+100;
int v[maxn],ans=0;//储存每个节点石子数量和异或累计
vector<int> a[maxn];//构建树结构
void zs(int x,int fa,int dep){//当前点、当前点的父亲、当前层数
if(dep%2!=0){//如果是奇数层
ans^=v[x];//累计异或值
}
for(int i=0;i<a[x].size();i++){//遍历当前点每一个连接的节点
int v=a[x][i];
if(v==fa) continue;//如果这个节点是来时经过的父亲,跳过递归,避免死循环(不走回头路)
zs(v,x,dep+1);//如果不是回头路,递归下一个节点
}
}
int main(){
int t;
cin>>t;
while(t--){
int n;
ans=0;
cin>>n;
for(int i=0;i<=n;i++) a[i].clear();//清空(有多组数据)
for(int i=1;i<n;i++){
int f;
cin>>f;
a[i].push_back(f);
a[f].push_back(i);//建立父子两边都可以通行的通道
}
for(int i=0;i<n;i++){//要从 0 到 n-1,因为"节点编号为 0 到 n-1 "(不然会错)
cin>>v[i];//输入每个节点的石子数量
}
zs(0,-1,0);//从根节点开始,暂定父节点为 -1 (没有),层数为 0
if(ans==0) cout<<"lose"<<endl;//如果异或值为 0,先手必输
else cout<<"win"<<endl;//否则先手必赢
}
return 0;
}

544

被折叠的 条评论
为什么被折叠?



