【杭电】“钉耙编程”(1)L-Play On Tree 题解

传送门:Play on Tree
标签:博弈

题目大意

规定一种在有根树上的博弈:二人轮流进行操作,每次可选择一个目前存在的节点,将以其为根的整个子树删除。选择主树根节点的一方败北。现在A和B两人在此基础上进行游戏,但规则稍有变化:游戏开始前由A决定树的根节点,然后B作为先手进行第一次操作。假设A选择的根节点是随机的,请你计算B获胜的概率(答案对1e9+7取模)。
输入:第一行一个正整数n(1<=n<=2e5),接下来n-1行每行两个正整数代表树上的一条边。
输出:B获胜的概率对1e9+7取模。

算法分析

  • 我们考虑由简到繁,先思考根节点固定的情况下如何计算。如果题目确定1为根节点,问A和B在最优策略下谁能获胜,我们肯定会想到树上dp,由叶子向根进行转移。再将问题继续简化:每一棵固定的树是否有必胜态和必败态?显然是的。那么主树的胜负态肯定是由子树决定的。我们只要想明白一个问题:子树满足什么条件时主树必胜?
  • 众所周知,博弈的本质要么是限制,要么是模仿。先以一堆石子上的nim游戏举例,如果每个人只能拿1-m个,那么无论对手拿多少个,我方都可以将一个回合的总石子消耗量稳定限制在m+1个,反之对方也是一样。所以先手只要拿走石子总数对m+1取模的结果就能获胜,这便是限制的博弈。再推广到n堆石子的nim游戏,每个人每次只能在某一堆中取1-m个石子,对手在A堆拿m个石子时,我们就可以在B堆中同样拿m个石子,只要这样的操作一直可以进行我方就是必胜的,这就是模仿的博弈。
  • 这道题明显是模仿的博弈,我们假设1为根节点,它连接了5个子树,其中两个子树处于必胜态,三个子树处于必败态,那么我们先手可以删掉一整个必败态的子树,然后剩下四个子树两两配对,无论对手怎么操作,我们都可以在相应的子树上模仿他,使得他最终必须取走根节点。也就是说先手必须创造出一种情况:必胜态子树和必败态子树的个数都分别为偶数。再假设初始时根节点连接了6个子树,其中三个为必胜态,三个为必败态。你可能认为先手无法获胜,但其实必胜态可以通过一步操作转移为必败态,使得必胜态子树数量变为两个,必败态子树变为四个。
  • 那怎么快速地判断先手能否将最初状态下创造出目标条件呢?根据奇偶判断。只要假设根节点连接了n个必胜态子树和m个必败态子树,只要n和m中有一个奇数,先手就必胜,这一点通过上面的结论很容易证明。再考虑sg函数,我们打表后会发现根节点的sg函数值为所有子树sg函数值+1的异或和。现在我们能O(n)判断一个根节点的胜负态,那要怎么判断n个根节点的胜负态呢?显然是用换根dp,而且是简化版的换根,dfs时只要传父节点的sg值就行了,整体算法复杂度为O(n)。

代码实现

#include <iostream>
using namespace std;
#include <vector>
vector<int> g[200005];
const int mod=1e9+7;
long long ans;
int dp[200005];

void dfs(int x,int fa){
    for(auto &i:g[x])
        if(i!=fa){
            dfs(i,x);
            dp[x]^=(dp[i]+1);
        }
}

void dfs1(int x,int fa,int last){
    int cnt0,cnt1;
    dp[x]^=(last+1);
    if(dp[x])ans++;
    for(auto &i:g[x])
        if(i!=fa)
            dfs1(i,x,dp[x]^(dp[i]+1));
}

long long pow_mod(long long a,long long b){
    long long x=1LL;
    a%=mod;
    while(b){
        if(b&1LL)x=x*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return x;
}

int main(){
    int x,y;
    long long i,j,n,mmi,k,m,T,p,now,pos;
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>T;
    while(T--){
        cin>>n;
        ans=0LL;
        for(i=0;i<n-1;i++){
            cin>>x>>y;
            g[x].emplace_back(y);
            g[y].emplace_back(x);
        }
        dfs(1,1);
        dfs1(1,1,-1);
        cout<<ans*pow_mod(n,mod-2)%mod<<'\n';
        for(i=1;i<=n;i++){
            dp[i]=0;
            g[i].clear();
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值