原来还可以这么写。
分开考虑子树内和子树外的贡献。
设 $f_u$ 表示 $u$ 子树内令 $u$ 必须染色,组成一个黑色连通块的方案数。
设 $g_u$ 表示 $u$ 子树外令 $u$ 必须染色,组成一个黑色连通块的方案数。
叶节点的 $f=1$,根节点的 $g=1$。
容易得到转移式:
$$f_u = \prod\limits_{v \in son_u} (f_v + 1)$$
$$g_u = g_{fa_u} \times \frac{f_{fa_u}}{f_u + 1} + 1$$
对于 $f$ 的转移式,本质上就是对于所有儿子 $v$,给 $v$ 染色的方案数 $f_v$ 加不给 $v$ 染色的方案数 $1$ 的乘积。
对于 $g$ 的转移式,意义是父节点的子树外与父节点除了 $u$ 儿子的方案数乘积,加上只给 $u$ 染色的方案数 $1$。
然后发现模数 $M$ 不一定是质数,所以不一定存在逆元,因此需要记录 $f$ 的前缀积和后缀积。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 15, M = N << 1;
int n, h[N], e[M], ne[M], idx = 0;
int son[N], mod = 0;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
long long f[N], g[N];
vector<int> pre[N], suf[N];
void dfs(int u, int father) {
vector<int> a; a.clear();
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == father) continue;
dfs(v, u);
son[u]++;
a.push_back(f[v] + 1);
}
long long F = 1ll;
for (int i = 0; i < a.size(); i++) {
pre[u].push_back(F);
(F *= a[i]) %= mod;
}
f[u] = F, F = 1;
for (int i = a.size() - 1; i >= 0; i--) {
suf[u].push_back(F);
(F *= a[i]) %= mod;
}
reverse(suf[u].begin(), suf[u].end());
}
void dfs2(int u, int father) {
int cnt = 0;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == father) continue;
g[v] = (g[u] * 1ll * pre[u][cnt] % mod * 1ll * suf[u][cnt] % mod + 1) % mod;
dfs2(v, u);
cnt++;
}
}
int main() {
scanf("%d%d", &n, &mod);
for (int i = 1; i <= n; i++) h[i] = -1;
for (int i = 1, u, v; i < n; i++) {
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
dfs(1, 0);
g[1] = 1, dfs2(1, 0);
for (int i = 1; i <= n; i++)
printf("%d\n", f[i] * 1ll * g[i] % mod);
return 0;
}