- 给定一棵(n)个点的树,对于每个点(i),求出(sum_{j=1}^ndist(i,j)^k)。
- (nle5 imes10^4,kle150)
第二类斯特林数与自然数幂和
可详见这篇博客:斯特林数的基础性质与斯特林反演的初步入门。
这里只给出结论:
[sum_{i=0}^n i^k=sum_{j=0}^kS_2(k,j)j!C_{n+1}^{j+1}
]
考虑我们可以枚举(j),则(S_2(k,j)j!)都是已知的,只要对于每个(j)求出(C_{n+1}^{j+1})即可。
换根(DP)
考虑先强制(1)号点为根,那么只要求出每个点子树内的答案。
如果当前点(x)与子树内一个点(y)距离为(dist(x,y)),那么(x)的子节点到(y)的距离就是(dist(x,y)-1)。
而众所周知(C_{dist(x,y)+1}^{j+1}=C_{dist(x,y)}^j+C_{dist(x,y)}^{j+1})。
我们用(f_{x,j})表示(x)子树内(C_{dist(x,y)+1}^{j+1})之和,那么根据上面这个式子就有转移:
[f_{x,j}=sum(f_{son,j-1}+f_{son,j})
]
然后只要再(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
",ans[i]);return 0;
}