【题解】[国家集训队] Crash 的文明世界

[国家集训队] Crash 的文明世界

\(\text{Solution:}\)

要用到的恒等式:

\[n^m=\sum_{k=0}^n C_{n}^k k! S(m,k) \]

考虑化式子:

\[Ans(i)=\sum_{j=1}^n dis(i,j)^m \]

\[=\sum_{j=1}^n \sum_{k=0}^{\min\{dis(i,j),m\}}C_{dis(i,j)}^k k! S(\min\{dis(i,j),m\},k) \]

\[=\sum_{k=0}^m k!S(m,k)\sum_{j=1}^n C_{dis(i,j)}^k \]

上面那个式子是直接默认 \(m\leq dis(i,j)\) 了。

一个细节: 第二类斯特林数的定义是划分成非空集合

所以才可以像上面的式子一样取 \(\min.\)

接下来观察一下后面的 \(C_{dis(i,j)}^k\) 可以理解成从一条路径中选择 \(k\) 条边的方案数。那这个就可以 \(dp\) 了。

\(f_{i,j}\) 表示子树 \(i\)\(i\) 的路径 选择 \(j\) 条边的方案数。这里之所以是到 \(i\) 的路径,是因为题目中给的式子是 \(dis(i,j)\) 从而我们可以把 \(i\) 当根做。

于是乎,尝试写出 \(dp\) 式子:

\[f_{i,j}=\sum f_{v,j}+f_{v,j-1} \]

意义就是:这条路径上选的边是不是一起选上边 \((v,j).\)

于是这个 \(dp\) 在上述枚举边界的限制下可以做到 \(O(nk)\)

那么现在只求出了一个点对应的 \(Ans(i)\) 怎么办?考虑大力换根:

观察一下,换根的套路一定是从根的孩子换到爹上面。考虑这样造成的影响:

对于要换上去的根 \(x\) 来说,贡献分成两部分:一部分是第一次 \(dp\) 时那棵树中它作为子树的部分;另一部分就是除它之外的部分。而注意到除它之外的部分除了它爹的子树外,还包括和它一起作为其父亲子树的部分。

那就考虑怎么把贡献换上去吧:选择了 \(i\) 条边,对应需要统计:

  • 换下去的 \(fa\) 作为其子树,它们之间的连边选不选:\(g_{fa,i-1}+g_{fa,i}\)

  • 统计原树中和它一同作为父亲子树的点的答案: \(f_{fa,i}+f_{fa,i-1}\)

  • 容斥掉多余的答案:对 \(f_{fa,i}\) 多余的部分是 \(f_{x,i}+f_{x,i-1},\) 对于 \(f_{fa,i-1}\) 多余的部分是 \(f_{x,i-1}+f_{x,i-2}\)

考虑到这三步,做一个换根就好了。还有一个细节:对换根十分不熟悉的菜鸡我写的时候直接就继承了 \(g_{fa}\) 的信息,但实际上是错误的:

主要原因在于,我脑海中想象的换根是已经把 \(fa\) 换上去了,而这时 \(x\) 就对应仅次于根的一棵树。但实际上并不是这样:如果是那样,完全不需要考虑 \(f_{fa}\) 的情况,因为和它同级别的子树其实也就等同于除去它之外的所有点了;但实际上并非如此,换根的时候我们并没有更新所有节点的 \(f,\) 这也意味着这个想法是错误的,实际上我们的树还是原来的模样,我们已经统计出了 \(fa\) 作为根的时候的信息,现在来考虑用原来树上的信息来更新出 \(x\) 作为根的信息。所以,我们需要在原树上进行分析,从而得出上述的结论,不能只凭脑子思考,不画图,这样很容易犯错。

统计出了 \(fa\) 作为根的答案实际上是有很多用处的,比如会发现它其实保留了 \(x\) 在原树中的结构;所以这上面很多信息其实是正确的,其他错误的信息也只是有一部分需要进行转移。

继承了 \(g_{fa}\) 之后思考的就应当是 \(x\) 当根和现在情况的影响:除了 \(x\) 子树内的点,到 \(x\) 的距离都加了 \(1.\)

这一部分考虑在继承的 \(g_{fa}\) 上面完成:可以先把之前的 \(f_{fa}\) 累加进去。因为这一部分在继承的时候是没有改变距离的,因为继承的时候根是父亲。

那么这样一加实际上又加多了,减去原来的部分就是算出 \(f_x\)\(f_{fa}\) 的贡献。

至于其他的部分,继承的时候稍微处理一下就行了。

至于斯特林数直接递推公式大力 \(O(k^2)\) 处理即可。

总复杂度 \(O(nk+k^2)\to O(nk).\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
const int mod=10007;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int dec(int x,int y){return (x-y+mod)%mod;}
int S[200][200],n,k,f[N][200],g[N][200];
int head[N],tot,Ans[N],fac[N];
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
struct E{int nxt,to;}e[N];
inline void add(int x,int y){
	e[++tot]=(E){head[x],y};
	head[x]=tot;
}
inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=Mul(res,a);
		a=Mul(a,a);b>>=1;
	} 
	return res;
}
void Spre(){
	S[0][0]=S[1][1]=1;
	for(int i=2;i<=k;++i)
		for(int j=1;j<=i;++j)
			S[i][j]=Add(S[i-1][j-1],Mul(j,S[i-1][j]));
	fac[0]=1;
	for(int i=1;i<=n;++i)fac[i]=Mul(fac[i-1],i);
}
void dfs(int x,int fa){
	f[x][0]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa)continue;
		dfs(j,x);
		for(int v=1;v<=k;++v)f[x][v]=Add(f[x][v],Add(f[j][v],f[j][v-1]));
		f[x][0]=Add(f[x][0],f[j][0]);
	}
}
void change(int x,int fa){
	if(fa){
		for(int i=0;i<=k;++i){
			if(i==0)g[x][i]=Add(g[fa][0],Add(f[fa][0],mod-f[x][0]));
			else if(i==1)g[x][i]=Add(g[fa][i],Add(g[fa][i-1],Add(f[fa][i],Add(f[fa][i-1],Add(mod-f[x][i],mod-f[x][i-1]+mod-f[x][i-1])))));
			else {
				g[x][i]=Add(g[fa][i],Add(g[fa][i-1],Add(f[fa][i],Add(f[fa][i-1],Add(mod-f[x][i],mod-f[x][i-1]+mod-f[x][i-1])))));
				g[x][i]=Add(g[x][i],mod-f[x][i-2]);
			}
		}
	}
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa)continue;
		change(j,x);
	}
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	Spre();
	dfs(1,0);change(1,0);
	for(int i=1;i<=n;++i){
		int Ans=0;
		for(int j=0;j<=k;++j){
			int v=Mul(fac[j],S[k][j]);
			Ans=Add(Ans,Mul(v,f[i][j]+g[i][j]));
		}
		printf("%d\n",Ans);
	}
	return 0;
} 
posted @ 2021-07-31 22:59  Refined_heart  阅读(30)  评论(0编辑  收藏  举报