D-Rebuild Tree
Prufer 是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复n−2n-2n−2次后就只剩下两个结点,算法结束。(为什么不是n−1n-1n−1次呢?因为第n−1n-1n−1次操作序列记录下的节点一定是nnn)
一个 nnn个点 mmm条边的带标号无向图有 kkk个连通块。我们希望添加k−1k-1k−1条边使得整个图连通。方案数为nk−2⋅∏i=1ksin^{k-2}·\prod_{i=1}^{k}s_ink−2⋅i=1∏ksi
证明考虑组合意义,详细见 OIWIKIPrufer 序列
- 有了上面结论,删kkk条边之后形成k+1k+1k+1个连通块,设每个连通块的大小为sis_isi
则生成树个数为nk−1⋅∏i=1k+1sin^{k-1}·\prod_{i=1}^{k+1}s_ink−1⋅∏i=1k+1si,该题就是求∑split(n,k)nk−1⋅∏i=1k+1si=nk−1⋅∑split(n,k)∏i=1k+1si\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_isplit(n,k)∑nk−1⋅i=1∏k+1si=nk−1⋅split(n,k)∑i=1∏k+1si - 求∑split(n,k)∏i=1k+1si\sum_{\text{split(n,k)}}\prod_{i=1}^{k+1}s_i∑split(n,k)∏i=1k+1si可以考虑将问题转化为等价问题:删掉kkk条边且在每个联通块选一个点的方案数(由于每个连通块有sis_isi种选择即得出∏i=1k+1si\prod_{i=1}^{k+1}s_i∏i=1k+1si)。
设计dp:
fu,j,0/1f_{u,j,0/1}fu,j,0/1表示uuu子树内,删了jjj条边,是否选择点的方案数。
#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);}