CodeForces Round #417 E Solution:Nim博弈

来源:互联网 发布:网页信息采集软件 编辑:程序博客网 时间:2024/06/14 20:45

第一次做博弈相关的题目,所以写的比较详细。


题意:给出一棵树,数据保证根为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;}


阅读全文
0 0
原创粉丝点击