树上 NIM 游戏

题目描述

你在和蒟蒻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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值