牛客挑战赛30 - C 小G砍树

题解 - 牛客挑战赛30 - C 小G砍树

  • 一道练习换根 D P DP 以及计数的好题。

题目意思

  • 小G砍树
  • 给你一个 n n 个节点的树,每次只能删除度为 1 1 的节点问删光所有节点的总方案数。
  • n 1 0 5 n\leq 10^5

S o l \mathrm{Sol}

  • 前置知识:换根DP,组合数
  • 我们如果把状态设为 f u f_u 表示最后一个删数 u u 节点的总方案数。但是子树之间有相互影响的关系,做不了。但是我们考虑一定是从下往上删的,其实往上删节点的过程可以认为是一个填编号的过程。我们设 S i z u Siz_u u u 节点的子树大小,那么 u u 节点的编号肯定为 S i z u Siz_u ,那么 u u 子树里的点(直接儿子)是可以随意填的。那么我们可以先预处理出 f u f_u 表示 u u 以及他的直接儿子都标记好标号的方案数。
  • 那么 f u = v u f v × C S i z u S i z v S i z v f_u=\prod_{v∈u}f_v\times C_{Siz_u-Siz_v}^{Siz_v} ,每次选一个儿子 v v ,要使 S i z u = S i z v Siz_u-=Siz_v
  • 以上都还算比较好理解,那么我们就开始利用换根 D P DP 来求出每个点的方案数 g u g_u ,那么答案就是 a n s = i = 1 n g i ans=\sum_{i=1}^{n} g_i
  • 其他博客没有清楚地讲换根的过程,我这里来讲一下:首先我们设 u u 为当前子树的根节点 v v 为即将要换根的节点,我们要如何从 u v u\to v 呢?
  • 我么首先把 v v u u 的贡献从 u u 中解除,这个比较好处理就是 v u = f u × ( 1 C n 1 s i z v × f v ) v\to u=f_u \times \left(\dfrac{1}{C_{n-1}^{siz_v}\times f_v}\right) ,因为在预处理时候加进去的要减掉。然后我们现在以 v v 为根了要加 u v u\to v 贡献,那么 u v = f v × C n 1 n s i z v × ( v u ) u\to v=f_v\times C_{n-1}^{n-siz_v} \times (v\to u) 。因为现在 v v 成为根了那么要把它新增子树的贡献加上去,这个可以和一开始的求方案树的柿子联系在一起思考。
  • 这样我们就做完了这道好题。

C o d e \mathrm{Code}

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
 
inline int read()
{
    int sum=0,ff=1; char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-') ff=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        sum=sum*10+(ch^48),ch=getchar();
    return sum*ff;
}
 
const int N=1e5+5;
const int mo=998244353;
 
int n,m,ans,siz[N],f[N],inv[N],jc[N];
vector<int> G[N];
  
inline int ksm(int x,int y)
{
    int ret=1ll;
    while(y)
    {
        if(y&1) ret=ret*x%mo;
        x=x*x%mo;
        y>>=1ll;
    }
    return ret;
}
 
inline int C(int x,int y)
{
    return jc[x]%mo*inv[x-y]%mo*inv[y]%mo;
}
 
inline void dfs(int u,int fa)
{
    f[u]=1,siz[u]=1;
    for ( int i=0;i<G[u].size();i++ )
    {
        int v=G[u][i];
        if(v==fa) continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
    int sum=0;
    for ( int i=0;i<G[u].size();i++ )
    {
        int v=G[u][i];
        if(v==fa) continue;
        f[u]=(f[u]*C(siz[u]-sum-1,siz[v])%mo*f[v]+mo)%mo;
        sum+=siz[v];
    }
}
 
inline void dp(int u,int fa)
{
    for ( int i=0;i<G[u].size();i++ )
    {
        int v=G[u][i];
        if(v==fa) continue;
        int las=(f[u]*ksm(C(n-1,siz[v]),mo-2)%mo)*ksm(f[v],mo-2)%mo;
        f[v]=f[v]*C(n-1,n-siz[v])%mo*las%mo;
        dp(v,u);
    }
}
 
signed main()
{
    n=read();
    jc[0]=inv[0]=1;
    for ( int i=1;i<=n;i++ ) jc[i]=jc[i-1]*i%mo;
    for ( int i=1;i<=n;i++ ) inv[i]=ksm(jc[i],mo-2)%mo;
    for ( int i=1;i<n;i++ )
    {
        int x,y;
        x=read(),y=read();
        G[x].pb(y);
        G[y].pb(x);
    }
    dfs(1,0);
    dp(1,0);
    for ( int i=1;i<=n;i++ ) ans=(ans+f[i])%mo;
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wangyiyang2/article/details/105582147