把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4827】[国家集训队] Crash 的文明世界(斯特林数+换根DP)

点此看题面

  • 给定一棵\(n\)个点的树,对于每个点\(i\),求出\(\sum_{j=1}^ndist(i,j)^k\)
  • \(n\le5\times10^4,k\le150\)

第二类斯特林数

根据第二类斯特林数的性质可以得到:

\[i^k=\sum_{j=0}^kS_2(k,j)j!C_{i}^{j} \]

考虑先强制\(1\)号点为根,那么只要求出每个点子树内的答案。

如果当前点\(x\)与子树内一个点\(y\)距离为\(dist(x,y)\),那么\(x\)的子节点到\(y\)的距离就是\(dist(x,y)-1\)

而众所周知\(C_{dist(x,y)}^{j}=C_{dist(x,y)-1}^j+C_{dist(x,y)-1}^{j-1}\)

我们用\(f_{x,j}\)表示\(x\)子树内\(C_{dist(x,y)}^{j}\)之和,那么根据上面这个式子就有转移:

\[f_{x,j}=\sum(f_{son,j}+f_{son,j-1}) \]

然后只要再\(dfs\)一遍做一次换根\(DP\)就好了。

代码:\(O(nk)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define K 150
#define X 10007
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,k,S2[K+5][K+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
int f[N+5][K+5];I void dfs1(CI x,CI lst=0)//第一遍dfs以1为根DP
{
	f[x][0]=1;for(RI i=lnk[x],j;i;i=e[i].nxt) if(e[i].to^lst) for(dfs1(e[i].to,x),
		j=0;j<=k;++j) f[x][j]=(0LL+f[x][j]+f[e[i].to][j]+(j?f[e[i].to][j-1]:0))%X;//DP转移
}
int g[K+5],ans[N+5];I void dfs2(CI x,CI lst=0)//第二遍dfs换根DP
{
	RI i,j,F=1;for(i=0;i<=k;F=1LL*F*(++i)%X) ans[x]=(1LL*S2[k][i]*F%X*f[x][i]+ans[x])%X;//计算以当前点为根的答案
	for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst)
	{
		for(j=0;j<=k;++j) g[j]=(2LL*X+f[x][j]-f[e[i].to][j]-(j?f[e[i].to][j-1]:0))%X;//消去该子节点贡献
		for(j=0;j<=k;++j) f[e[i].to][j]=(0LL+f[e[i].to][j]+g[j]+(j?g[j-1]:0))%X;dfs2(e[i].to,x);//更新子节点DP数组并继续DP
	}
}
int main()
{
	RI i,j,x,y;for(scanf("%d%d",&n,&k),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	for(S2[0][0]=i=1;i<=k;++i) for(j=1;j<=i;++j) S2[i][j]=(1LL*j*S2[i-1][j]+S2[i-1][j-1])%X;//预处理第二类斯特林数
	for(dfs1(1),dfs2(1),i=1;i<=n;++i) printf("%d\n",ans[i]);return 0;
}
posted @ 2020-11-11 19:52  TheLostWeak  阅读(88)  评论(0编辑  收藏  举报