2021牛客暑期多校训练营4 D-Rebuild Tree(prufer序列+树形dp)

22 篇文章 0 订阅

D-Rebuild Tree

Prufer 是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复 n − 2 n-2 n2次后就只剩下两个结点,算法结束。(为什么不是 n − 1 n-1 n1次呢?因为第 n − 1 n-1 n1次操作序列记录下的节点一定是 n n n

一个 n n n个点 m m m条边的带标号无向图有 k k k个连通块。我们希望添加 k − 1 k-1 k1条边使得整个图连通。方案数为 n k − 2 ⋅ ∏ i = 1 k s i n^{k-2}·\prod_{i=1}^{k}s_i nk2i=1ksi
证明考虑组合意义,详细见 OIWIKIPrufer 序列

  • 有了上面结论,删 k k k条边之后形成 k + 1 k+1 k+1个连通块,设每个连通块的大小为 s i s_i si
    ​则生成树个数为 n k − 1 ⋅ ∏ i = 1 k + 1 s i n^{k-1}·\prod_{i=1}^{k+1}s_i nk1i=1k+1si,该题就是求 ∑ split(n,k) n k − 1 ⋅ ∏ i = 1 k + 1 s i = n k − 1 ⋅ ∑ split(n,k) ∏ i = 1 k + 1 s i \sum_{\text{split(n,k)}}n^{k-1}·\prod_{i=1}^{k+1}s_i=n^{k-1}·\sum_{\text{split(n,k)}}\prod_{i=1}^{k+1}s_i split(n,k)nk1i=1k+1si=nk1split(n,k)i=1k+1si
  • ∑ split(n,k) ∏ i = 1 k + 1 s i \sum_{\text{split(n,k)}}\prod_{i=1}^{k+1}s_i split(n,k)i=1k+1si可以考虑将问题转化为等价问题:删掉 k k k条边且在每个联通块选一个点的方案数(由于每个连通块有 s i s_i si种选择即得出 ∏ i = 1 k + 1 s i \prod_{i=1}^{k+1}s_i i=1k+1si)。

设计dp:
f u , j , 0 / 1 f_{u,j,0/1} fu,j,0/1表示 u u u子树内,删了 j j j条边,是否选择点的方案数。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=50010,mod=998244353;
int n,m;
vector<int> e[N];
ll qmi(ll a,ll b)
{
    ll v=1;
    while(b)
    {
        if(b&1) v=v*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return v;
}
ll f[N][105][2];
ll g[105][2];
int sz[N];
void dfs(int u,int fa)
{
    sz[u]=1;
    f[u][0][0]=f[u][0][1]=1;
    for(auto v:e[u])
    {
        if(v==fa) continue;
        
        dfs(v,u);
        memset(g,0,sizeof g);
        
        for(int i=0;i<=min(sz[u]-1,m);i++)
            for(int j=0;j<sz[v]&&i+j<=m;j++)
            {
                g[i+j][0]=(g[i+j][0]+f[u][i][0]*f[v][j][0]%mod)%mod;
                g[i+j][1]=(g[i+j][1]+f[u][i][0]*f[v][j][1]%mod+f[u][i][1]*f[v][j][0]%mod)%mod;
                
                if(i+j==m) continue;
                
                g[i+j+1][0]=(g[i+j+1][0]+f[u][i][0]*f[v][j][1]%mod)%mod;
                g[i+j+1][1]=(g[i+j+1][1]+f[u][i][1]*f[v][j][1]%mod)%mod;
                
            }
        sz[u]+=sz[v];
        memcpy(f[u],g,sizeof g);
    }
}
int main()
{
    n=rd(),m=rd();
    for(int i=1;i<n;i++)
    {
        int u=rd(),v=rd();
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1,0);
    printf("%lld\n",f[1][m][1]*qmi(n,m-1)%mod);
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值