P6803 [CEOI 2020] 星际迷航

洛谷

由于两个人都是绝对聪明的,所以每个人都只会做出最好的选择。

由于这个游戏在加完星门以后的情况已经确定了,所以开始时的树的形态以及开的星门就会直接导致游戏的胜负。

那么我们可以先处理出在根的胜负。

我们记录状态 \(dp_i\)\(i\)\(0\) 或者 \(1\),分别表示在这个点先手一定赢还是一定输。

怎么样可以知道是赢还是输呢?

如果我这一步走到一个先手必定输的点,那么我经过这一次聪明的选择就可以直接拿下对手,反之如果我走了先手必赢的点,那么对手就一定可以把我拿下。

那么情况就显然了,如果这个点的儿子中有必输点,那么这个点就必赢,反之必输。

那么我们可以直接在 \(O(n)\) 内求出根是必赢还是必输。

但是还有一个星门操作对于答案可能产生影响。

我们先考虑 \(D\) 的值为 \(1\) 的情况。

此时我们连接一条边,需要知道这个点为根是必赢还是必输。

虽然树的形态相同,但是我们并不知道这个点到底是必赢点还是必输点因为我们只处理了这个点的子树内的情况。

这时很容易想到处理的方式就是换根 DP。

这时我们就得到了连接的点是必赢还是必输。

我们还可以发现只有在两个连接点都必输才能对前一棵树的连接点产生变化。

然而这样并不一定能够修改答案,因为我们的这一次变化必须对这个点的祖先产生影响。

明显我们希望得出以这个点为根的树内有多少个点修改可以对于这棵树的结果产生影响,这样才能够得到通过星门改变结果的方案数。

我们记录一个数组 \(s_i\) 表示儿子中的必输点数量。

如果此时的 \(s_i\)\(0\) 的话,那么这个点必输,我们直接修改它的一个儿子,变成必输,或者直接更改此节点,那么这个点就变为必胜了。

如果此时的 \(s_i\)\(1\) 的话,此时这个点必赢,我们直接将那个必输点修改掉即可。

如果此时的 \(s_i\) 大于 \(1\) 的话,那么此时这个点一定是必赢,无法更改。

那么就处理完成了,再来一个换根就完事了。

那么我们就可以得到一个有效修改的节点数。

设在 \(i\) 为根时更改有效的节点数为 \(f_i\)

得到这个以后,我们再处理出最开始以这个节点为根的必赢和必输数量,就可以得到最终必赢和必输的情况。

式子可以自己推一下,考虑这个点是变还是不变即可。

此时再考虑有更大的 \(D\) 的情况就很简单了,我们已经得到了新的根必胜和必败方案了,然后我们下一次操作本质相同,只需要将必赢和必输点的数量更新即可。

十分入机的加法乘法,简单的递推式,再加上巨大的操作次数,很容易就能想到矩阵优化。

那么直接上矩阵就拿下了。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int n,d,dp[100005],sum[100005][2],f[100005],s[100005],cnt;
vector<int> e[100005];
struct MT{
	int c[5][5],n,m;
	MT(){
		n=m=0;
		memset(c,0,sizeof(c));
	}
	MT friend operator*(MT a,MT b){
		MT c;
		c.n=a.n,c.m=b.m;
		for(int i=1;i<=a.n;i++){
			for(int j=1;j<=b.m;j++){
				for(int k=1;k<=a.m;k++){
					c.c[i][j]+=(a.c[i][k]*b.c[k][j])%mod;
					c.c[i][j]%=mod;
				}
			}
		}
		return c;
	}
}base,be;
void ksm(MT a,int b){
	while(b){
		if(b&1)be=be*a;
		a=a*a;
		b>>=1;
	}
}
void dfs(int p,int fa){
	for(int i:e[p]){
		if(i==fa)continue;
		dfs(i,p);
		if(!dp[i])s[p]++;
		sum[p][dp[i]]+=f[i];
	}
	if(s[p])dp[p]=1;
	else dp[p]=0;
	if(!s[p])f[p]=sum[p][1]+1;
	else if(s[p]==1)f[p]=sum[p][0];
	else f[p]=0;
}
void dfs2(int p,int fa){
	if(!dp[p])cnt++;
	int dpp=dp[p],sump0=sum[p][0],sump1=sum[p][1],fp=f[p],sp=s[p];
	for(int i:e[p]){
		if(i==fa)continue;
		if(!dp[i])s[p]--;
		sum[p][dp[i]]-=f[i];
		if(s[p])dp[p]=1;
		else dp[p]=0;
		if(!s[p])f[p]=sum[p][1]+1;
		else if(s[p]==1)f[p]=sum[p][0];
		else f[p]=0;
		swap(i,p);
		if(!dp[i])s[p]++;
		sum[p][dp[i]]+=f[i];
		if(s[p])dp[p]=1;
		else dp[p]=0;
		if(!s[p])f[p]=sum[p][1]+1;
		else if(s[p]==1)f[p]=sum[p][0];
		else f[p]=0;
		swap(i,p);
		dfs2(i,p);
		dp[p]=dpp,sum[p][0]=sump0,sum[p][1]=sump1,f[p]=fp,s[p]=sp;
	}
}
signed main(){
	cin>>n>>d;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	dfs2(1,0);
	base.n=base.m=2;
	for(int i=1;i<=n;i++){
		if(dp[i]){
			base.c[1][1]=(base.c[1][1]+f[i])%mod;
			base.c[1][2]=(base.c[1][2]+n-f[i])%mod;
			base.c[2][2]=(base.c[2][2]+n)%mod;
		}
		else {
			base.c[1][1]=(base.c[1][1]+n-f[i])%mod;
			base.c[2][1]=(base.c[2][1]+n)%mod;
			base.c[1][2]=(base.c[1][2]+f[i])%mod;
		}
	}
	be.n=1,be.m=2;
	be.c[1][1]=cnt,be.c[1][2]=n-cnt;
	ksm(base,d-1);
	if(dp[1])cout<<(((n-f[1])*be.c[1][1])%mod+(n*be.c[1][2])%mod)%mod;
	else cout<<(f[1]*be.c[1][1])%mod;
	return 0;
}

posted @ 2025-12-07 12:57  huhangqi  阅读(5)  评论(0)    收藏  举报