第一次做博弈相关的题目,所以写的比较详细。
题意:给出一棵树,数据保证根为1号点,每个点上有一些石子,树的形态保证:根到每个叶子结点的路径长度奇偶性相同,也就是说叶子结点的深度彼此相差2或者4或者6等等。
博弈规则:选取任意非0的点,如果是叶子结点,那么取出任意多个石子吃掉;如果不是叶子节点,取出任意多个石子加到一个孩子上去,首先出现无法操作的人输。
题解:这是典型的Nim博弈,那么关键在于SG函数的求解。
按照一般思路:先找terminal position,显然terminal position是整个树一个石子也不剩的情况。
往前推一步得到一些N position,那么显然,只有一个非0叶子节点是N position
继续想:如果只有叶子结点非0,那么就转化为经典的Nim石子博弈,当抑或和!=0的时候是N position,反之是P position。
在深入想:如果现在有非0的叶子节点,也有非0的高度为1的节点(叶子结点高度为0)。那么无论上一层是什么情况,胜负手只取决于叶子这一层:如果叶子层抑或和是0,就是P position 否则就是N position(必胜),因为如果叶子节点抑或和不是0,那么我可以吃掉叶子来达到Nim平衡,而对方无论是下放石子还是吃掉石子都会破坏Nim平衡,所以我是必胜的。相反如果抑或和是0,我只能打破Nim平衡,则对方必胜。
现在思路渐渐清晰了,和叶子节点的deep奇偶性相同的节点我们叫他 偶数层节点,其他的叫做 奇数层节点的话,如果我偶数层节点有Nim平衡的话,那么我的任何操作都会破坏这个平衡,然后对手永远都可以恢复这个平衡,随着石子不断下放,只剩下叶子这一层的时候,转化为最最经典的Nim博弈,则我是必输的。相反,如果偶数层节点没有Nim平衡,那么我必有方法恢复Nim平衡,使得对手被迫破坏Nim平衡,同理,我必胜。
现在分析得知:先遍历一次树,计算好每个点的deep,把偶数层节点的抑或和算出来,如果是0先手必败,否则先手必胜。题目要求允许你做一次交换,交换任意不同两点的石子数,问有多少种交换方式使得后手必胜。(u,v)和(v,u)算作一种。
剩下的就是一个trick了,如果我们算出来的抑或和K!=0,那么说明后手必败,必须在偶数层和奇数层之间做交换,才可能使得K==0,假设交换的偶数层的那个点石子数是a,奇数层的是b,那么交换之后的新的K等于K^b^a=0于是简单变形就是:K^a=b,那么我们可以这么做:在遍历的时候,每个偶数层的点,把他扔进一个Vector里面,每个奇数层的点我们开一个桶统计一下出现次数。然后对每个vector里面的元素x,我们让ans+=count[ K^( x ) ]就好了。
如果我们算出来的抑或和K==0,那么我们允许三种交换:偶数层任意两个点做交换,奇数层任意两个点做交换,偶数层和奇数层相同的点之间做交换,我们发现最后一种情况直接用上边的方法就行了。然后再加两次答案就好了。
AC代码:
#include<bits/stdc++.h>
using namespace std;
int can[17000005];
vector<int> a;
int father[100005];
int n;
bool leaf[100005];
int que[1000000];
int deep[100005];
int aa[100005];
int main(){
cin>>n;
memset(leaf,true,sizeof(leaf));
for (int i=1;i<=n;i++){
scanf("%d",&aa[i]);
}
for (int i=2;i<=n;i++){
scanf("%d",&father[i]);
leaf[father[i]]=false;
}
int l=0;
int r=0;
for (int i=1;i<=n;i++){
if (leaf[i]){
r++;
que[r]=i;
}
}
while (l<r){
l++;
int q=que[l];
if (deep[father[q]]==0&&q!=1){
r++;
deep[father[q]]=deep[q]+1;
que[r]=father[q];
}
}
int k=0;
for (int i=1;i<=n;i++){
if ((deep[i]&1)==0){
k^=aa[i];
a.push_back(aa[i]);
}else{
can[aa[i]]++;
}
}
long long ans=0;
if (k==0){
long long temp = a.size();
ans+=(temp*(temp-1))/2;
temp = n-temp;
ans+=(temp*(temp-1))/2;
}
for (vector<int>::iterator it = a.begin();it!=a.end();it++){
ans+=can[k^(*it)];
}
cout<<ans<<endl;
return 0;
}