题意
给你一棵树,树之间是无向边,有 \(d + 1\) 棵这样的树,相邻两个树之间可以选择一条有向边相连,现在有两个人在玩游戏,需要轮流走没有走过的点,走不了的人就输了,问有多少种连有向边的方式,使得先手必胜。
思路
我们先考虑只有一棵树怎么办。
做法显然,大力 dp 来存是不是先手必胜即可。
那我如果添加一棵树,我们就需要考虑我新加入的一条有向边对答案的影响。
我们设 \(r_i\) 表示有多少个点作为这个有向边的起点可以使得 \(i\) 的输赢翻转。
我们先考虑子树中的 \(r_i\) 咋求,对于必败点,我们需要让任意一个子节点的必胜点反转,对于必胜点,如果他的子节点中有 \(2\) 个及以上的必败点,那么肯定是修改不了的,否则,我们就需要让这个必胜点反转。
那么对于 \(i \neq 1\) 的情况,换根即可。
不妨认为我们已经会求 \(r\) 数组了,那么 \(d = 1\) 的答案就是,以下我们记录 \(suc\) 为必胜点,\(fal\) 为必败点:
那我们再考虑 \(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 Airfreopen(".in","r",stdin);freopen(".out","w",stdout);
#endifios::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;
}