传送门: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;
}