https://ac.nowcoder.com/acm/problem/22732
终于把这题补了。。
首先是组合数学的部分,设dp[i]表示以i为根的树的答案(合法排列数),对于根节点为x的树来说,这棵树的所有子节点可以形成((size_x – 1)!)种排列,假设节点y与节点x直接相连,则节点y能形成(size_y!)种排列,但是其中只有dp[y]种是合法的,所以((size_x – 1)!)需要先除掉(size_y!),再乘上dp[y],才是以y为根的子树对dp[x]的真正贡献。dp[i]更新方向为自底向上。
其次是换根部分,对于任意要提到根节点的节点我们都认为它的父亲就是原来的根节点。既然x一定为根,那么(size_x)就一直等于n,dp’[x]就等于dp[x]先除掉((n – 1)!),再乘上((n – 1 – size_y)!),再乘上原来子树y的贡献的倒数就好了。dp’[y]就等于dp[y]先除掉((size_y – 1)!),再乘上((n – 1)!),再乘上新子树(原来的父亲成为它的子树)的贡献即(\frac {dp’[x]} {(n – size_y)!})就好了,然后再把子节点更新一下,继续dfs,此时dp[i]的更新方向自顶向下。
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int mod = 998244353, inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;
vector<int> g[maxn];
int sz[maxn], n;
ll fac[maxn], dp[maxn], sum;
ll quickpow(ll x, ll k)
{
ll res = 1;
while (k){
if (k & 1) res = (res * x) % mod;
x = (x * x) % mod;
k >>= 1;
}
return res;
}
void dfs(int u, int p)
{
sz[u] = dp[u] = 1;
for (int v : g[u]){
if (v == p) continue;
dfs(v, u);
sz[u] += sz[v];
dp[u] *= dp[v] * quickpow(fac[sz[v]], mod - 2) % mod;
dp[u] %= mod;
}
dp[u] *= fac[sz[u] - 1];
dp[u] %= mod;
return ;
}
void dfs2(int u, int p)
{
sum += dp[u]; sum %= mod;
for (int v : g[u]){
if (v == p) continue;
ll t = dp[u] * quickpow(fac[n - 1], mod - 2) % mod
* fac[n - 1 - sz[v]] % mod
* fac[sz[v]] % mod * quickpow(dp[v], mod - 2) % mod;
dp[v] = fac[n - 1] * quickpow(fac[sz[v] - 1], mod - 2) % mod
* t % mod * quickpow(fac[n - sz[v]], mod - 2) % mod * dp[v] % mod;
dfs2(v, u);
}
}
int main()
{
fac[0] = 1;
for (int i = 1; i < maxn; ++i) fac[i] = fac[i - 1] * i % mod;
int l, r;
cin >> n;
for (int i = 1; i < n; ++i){
cin >> l >> r;
g[l].push_back(r);
g[r].push_back(l);
}
dfs(1, 0);
dfs2(1, 0);
cout << sum << endl;
return 0;
}