NOWCODER – 小G砍树(组合数学 + 换根dp)

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值