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


一、题目

点此看题

二、解法

要求的是这个柿子:

\[\sum_{j=1}^ndis(x,j)^k \]

一看这个 \(dis(x,j)^k\) 然后 \(k\) 很小就知道是套路题了,直接第二类斯特林数反演:

\[\sum_{j=1}^n\sum_{i=0}^kS(k,i)\times i!\times C(dis(x,j),i) \]

然后略微的变换一下,因为 \(S(k,i)\times i!\)\(j\) 没有一点关系,所以可以提到前面去:

\[\sum_{i=0}^kS(k,i)\times i!\sum_{j=1}^nC(dis(x,j),i) \]

由于 \(k\) 很小,前面那些东西可以直接算,问题是求后面的 \(f(x,i)=\sum_{j=1}^n C(dis(x,j),i)\) ,直接算会比较难,但是你发现 这个东西是放在树上的 ,这个关键条件没有用到啊!如果能通过递推的方式算出 \(f(x,i)\) 就好了。

因为 \(C(dis(x,j),i)=C(dis(x,j)-1,i)+C(dis(x,j)-1,i-1)\) ,设 \(y\)\(x\) 的儿子,那么有递推式:

\[f(x,i)=f(y,i)+f(y,i-1) \]

但是你发现这个只对 \(x\) 是根的情况成立,所以外面还要套一个换根 \(dp\) ,这个很容易写吧,时间复杂度 \(O(nk)\)

\(\tt UPD 2021/10/29\):也可以直接用组合意义做,相当于从路径上选出 \(i\) 个点,我们枚举另一个端点从哪个子树中来,然后讨论点 \(u\) 选不选,也可以得到上面的转移。

#include 
const int M = 50005;
const int MOD = 10007;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,tot,f[M],ans[M],fac[155],s[155][155];
int dp[M][155],t1[M][155],t2[M][155];
struct edge
{
	int v,next;
	edge(int V=0,int N=0) : v(V) , next(N) {} 
}e[2*M];
void dfs(int u,int fa)
{
	dp[u][0]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		for(int j=1;j<=k;j++)
			dp[u][j]=(dp[u][j]+dp[v][j]+dp[v][j-1])%MOD;
		dp[u][0]=(dp[u][0]+dp[v][0])%MOD;
	}
}
void dfs2(int u,int fa)
{
	for(int i=0;i<=k;i++)
		ans[u]=(ans[u]+1ll*s[k][i]*fac[i]%MOD*dp[u][i])%MOD;
	for(int w=f[u];w;w=e[w].next)//换根 
	{
		int v=e[w].v;
		if(v==fa) continue;
		for(int i=0;i<=k;i++)
			t1[u][i]=dp[u][i],t2[u][i]=dp[v][i];
		//先把v的贡献从u中删掉
		dp[u][0]-=dp[v][0];
		for(int i=1;i<=k;i++)
			dp[u][i]=(dp[u][i]-dp[v][i]-dp[v][i-1])%MOD;
		//在把u加到v里面去,分开写 
		dp[v][0]+=dp[u][0];
		for(int i=1;i<=k;i++)
			dp[v][i]=(dp[v][i]+dp[u][i]+dp[u][i-1])%MOD;
		dfs2(v,u);
		//还原,回溯
		for(int i=0;i<=k;i++)
			dp[u][i]=t1[u][i],dp[v][i]=t2[u][i]; 
	}
}
signed main()
{
	n=read();k=read();
	s[0][0]=fac[0]=1;
	for(int i=1;i<=k;i++)
		fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=1;i<=k;i++)
		for(int j=1;j<=k;j++)
			s[i][j]=(1ll*s[i-1][j]*j+s[i-1][j-1])%MOD; 
	for(int i=1;i

相关