[CEOI 2020] 星际迷航

题意

给你一棵树,树之间是无向边,有 \(d + 1\) 棵这样的树,相邻两个树之间可以选择一条有向边相连,现在有两个人在玩游戏,需要轮流走没有走过的点,走不了的人就输了,问有多少种连有向边的方式,使得先手必胜。

思路

我们先考虑只有一棵树怎么办。

做法显然,大力 dp 来存是不是先手必胜即可。

那我如果添加一棵树,我们就需要考虑我新加入的一条有向边对答案的影响。

我们设 \(r_i\) 表示有多少个点作为这个有向边的起点可以使得 \(i\) 的输赢翻转。

我们先考虑子树中的 \(r_i\) 咋求,对于必败点,我们需要让任意一个子节点的必胜点反转,对于必胜点,如果他的子节点中有 \(2\) 个及以上的必败点,那么肯定是修改不了的,否则,我们就需要让这个必胜点反转。

那么对于 \(i \neq 1\) 的情况,换根即可。

不妨认为我们已经会求 \(r\) 数组了,那么 \(d = 1\) 的答案就是,以下我们记录 \(suc\) 为必胜点,\(fal\) 为必败点:

\[ans=\left\{ \begin{aligned} suc \times n + (n - r_1) \times fal & & {1 \in \{suc \} } \\ r_1 \times fal & & {1 \in \{ fal \} } \end{aligned} \right. \]

那我们再考虑 \(d > 1\) 的情况,我们设 \(suc\) 为必胜点的贡献\(fal\) 同理,那么式子依然同上。

现在我们只需要统计 \(suc\)\(fal\) 的答案就行了,我们先记录一个数组 \(suc\)\(fal\) 表示只考虑后 \(i\) 棵树的贡献,注意到 \(suc_i\)\(fal_i\) 可以通过枚举每个点从而拆成 \(suc_{i - 1}\)\(fal_{i - 1}\) 来乘上一个跟 \(i\) 无关的数来转移,上个矩阵乘法就做完了,矩阵什么的就自己推吧。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{inline int read(){int f=1,t=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}return t*f;}inline void write(int x){if(x<0){putchar('-');x=-x;}if(x>=10){write(x/10);}putchar(x%10+'0');}}
using namespace io;

const int N = 1e5 + 10, MOD = 1e9 + 7;
struct Matr{
	int n, m;
	int a[3][3];
	friend Matr operator * (Matr x, Matr y){
		Matr ans;
		ans.n = x.n;
		ans.m = y.m;
		for(int i = 1; i <= x.n; i++){
			for(int j = 1; j <= y.m; j++){
				ans.a[i][j] = 0;
				for(int k = 1; k <= x.m; k ++){
					ans.a[i][j] += x.a[i][k] * y.a[k][j];
					ans.a[i][j] %= MOD;
				}
			}
		}
		return ans;
	}
};
Matr fir;
int n, d;
Matr quick_pow(Matr a, int b){
	if(b == 0) return fir;
	if(b % 2){
		return a * quick_pow(a, b - 1);
	}
	else{
		Matr tmp = quick_pow(a, b / 2);
		return tmp * tmp;
	}
}
vector<int>e[N];
int dp[N];
int cnt[N];
int rr[N];
int sum[N][2];
void dfs(int now, int fa){
	int tot = 0;
	for(auto y: e[now]){
		if(y == fa) continue;
		dfs(y, now);
		if(!dp[y]){
			dp[now] = 1;
			tot ++;
		}
		sum[now][dp[y]] += rr[y];
	}
	cnt[now] = tot;
	if(!dp[now]){
		rr[now] = sum[now][1] + 1;//修改子树内必胜点 + 修改自己
	}
	if(tot == 1){
		rr[now] = sum[now][0]; //修改子树内的必败点
	}
}
int tot;
int trr[N], trdp[N];
void dfs_tr(int now, int fa){
	if(!dp[now]){
		tot ++;// 一个合法的失败点
	}
	trr[now] = rr[now];
	trdp[now] = dp[now];
	for(auto y: e[now]){
		if(y == fa) continue;
		int rcnt = cnt[now], rdp = dp[now], rsum0 = sum[now][0], rsum1 = sum[now][1], rrr = rr[now];
		cnt[now] -= (dp[y] == 0);
		dp[now] = (cnt[now] > 0);
		sum[now][dp[y]] -= rr[y];
		if(!dp[now]){
			rr[now] = sum[now][1] + 1;//修改子树内必胜点 + 修改自己
		}
		else if(cnt[now] == 1){
			rr[now] = sum[now][0]; //修改子树内的必败点
		}
		else {
			rr[now] = 0; //改不了
		}
		int rcnty = cnt[y], rdpy = dp[y], rsum0y = sum[y][0], rsum1y = sum[y][1], rrry = rr[y];
		cnt[y] += (dp[now] == 0);
		dp[y] = (cnt[y] > 0);
		sum[y][dp[now]] += rr[now];
		if(!dp[y]){
			rr[y] = sum[y][1] + 1;//修改子树内必胜点 + 修改自己
		}
		else if(cnt[y] == 1){
			rr[y] = sum[y][0]; //修改子树内的必败点
		}
		else {
			rr[y] = 0; //改不了
		}
		dfs_tr(y, now);
		cnt[now] = rcnt, dp[now] = rdp, sum[now][0] = rsum0, sum[now][1] = rsum1, rr[now] = rrr;
		cnt[y] = rcnty, dp[y] = rdpy, sum[y][0] = rsum0y, sum[y][1] = rsum1y, rr[y] = rrry;
	}
}
signed main() {
#ifndef Air
	freopen(".in","r",stdin);
	freopen(".out","w",stdout);
#endif
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	fir.n = fir.m = 2;
	for(int i = 1; i <= 2; i++){
		for(int j = 1; j <= 2; j++){
			fir.a[i][j] = 0;
		}
	}
	fir.a[1][1] = fir.a[2][2] = 1;
	n = read();
	d = read();
	for(int i = 1; i < n; i++){
		int x = read(), y = read();
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1, 0);
	dfs_tr(1, 0);
	Matr np, tp;
	np.n = 1;
	np.m = 2;
	tp.n = 2;
	tp.m = 2;
	for(int i = 1; i <= 2; i++){
		for(int j = 1; j <= 2; j++){
			tp.a[i][j] = 0;
		}
	}
	for(int i = 1; i <= n; i++){
		if(trdp[i] == 0){
			tp.a[1][1] += n - trr[i];
			tp.a[2][1] += n;
			tp.a[1][2] += trr[i];
		}
		else{
			tp.a[1][1] += trr[i];
			tp.a[1][2] += n - trr[i];
			tp.a[2][2] += n;
		}
		for(int j = 1; j <= 2; j++){
			for(int k = 1; k <= 2; k++){
				tp.a[j][k] %= MOD;
			}
		}
	}
	tp = quick_pow(tp, d - 1);
	np.a[1][1] = tot;
	np.a[1][2] = n - tot;//最初的胜点与输点
	np = np * tp;
	int suc = np.a[1][2], fal = np.a[1][1];
	if(trdp[1]){
		cout << ((n - rr[1]) * fal % MOD + n * suc % MOD) % MOD;
	}
	else{
		cout << (rr[1] * fal) % MOD;
	}
	return 0;
}
posted @ 2025-10-30 21:37  Air2011  阅读(17)  评论(0)    收藏  举报