CodeForces Round #417 E Solution:Nim博弈

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


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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值