题目链接:https://ac.nowcoder.com/acm/contest/375/C
这是第二次做到换根树dp了,第一次是在cf上,转移公式比较简单,当场做了出来。
大体题意:给一棵带编号无根树,问有多少种方式将这棵树删到仅剩一个节点。每次仅能删除叶子节点。
首先,可以确定这是一个树形dp。dp[x]表示最后删除x的方案数,我们把x当做根,就可以通过换根在O(n)内求出dp[1-n]。
大体思路确定后,开始进行第一步。假如已经确定树根,那么应该怎么求dp[root]。这个状态肯定要从dp[子树]转移过来,然后就不会做了,考虑到子树之间存在的相互影响,根本无从下手。
然后就到了该题的核心思想:我们把删除顺序改成给节点分配编号。假如要求dp[u],那么u肯定是最后一个删除的(dp数组的定义),所以u的编号肯定为siz[u]。子树v的对于u的贡献就是 C(剩余编号,siz[v]) * dp[v](想想为什么是dp[v]而不是size[v]!) 。
这样我们就解决了固定树根的问题,主要是把无从下手的删除节点,改为容易理解推算的编号分配。
接着就可以推算换根公式了,画画图算一下就出来了。
#include <bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pb push_back
#define sec second
#define fir first
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,a,b) for(int i = (a); i >= (b); i--)
#define all(x) (x).begin(), (x).end()
#define SZ(x) ((int)(x).size())
#define pii pair<int,int>
using namespace std;
const int N = 1e5+100;
const int mod = 998244353;
vector<int>nxt[N];
ll fac[N],dp[N],siz[N],ans;
int n;
ll pow_mod(ll x,ll y) {
ll ans = 1;
while(y) {
if(y&1) ans = (ans*x)%mod;
x = (x*x)%mod;
y >>= 1;
}
return ans%mod;
}
ll inv(ll x) {
return pow_mod(x,mod-2);
}
ll C(ll n,ll m) {
return ((fac[n]*inv(fac[m])%mod)*inv(fac[n-m]))%mod;
}
void dfs(int u,int f) {
ll sum = 0;
siz[u] = 1;
dp[u] = 1;
for(auto v:nxt[u]) {
if(v==f) continue;
dfs(v,u);
siz[u] += siz[v];
}
for(auto v:nxt[u]) {
if(v==f) continue;
dp[u] = dp[u]*C(siz[u]-1-sum,siz[v])%mod*dp[v]%mod;
sum += siz[v];
}
}
void dfs2(int u,int f) {
ans += dp[u];
ans %= mod;
for(auto v:nxt[u]) {
if(v==f) continue;
ll x = dp[u]*inv(C(n-1,siz[v]))%mod*inv(dp[v])%mod;
dp[v] = dp[v]*C(n-1,n-siz[v])%mod*x%mod;
dfs2(v,u);
}
}
int main() {
freopen("a.txt","r",stdin);
ios::sync_with_stdio(0);
cin>>n;
fac[0] = 1;
rep(i, 1, 1e5)
fac[i] = (fac[i-1]*i)%mod;
rep(i, 1, n-1) {
int u, v;
cin>>u>>v;
nxt[u].pb(v);
nxt[v].pb(u);
}
dfs(1,0);
dfs2(1,0);
cout<<ans;
return 0;
}