牛客网-小G砍树【换根树dp】

本文介绍了如何使用换根树DP解决一类树状结构的动态规划问题,具体涉及题目为牛客网的一道题目,内容包括:题目链接、问题描述、解题思路以及核心思想的解释,即通过改变删除顺序为节点编号分配来简化问题,从而推导出动态转移方程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值