HDU 4625 JZPTREE 数学 - 斯特林数学习笔记

题目大意:给你一颗树,对每一个点x求所有点到其距离的k次方之和。 n ≤ 50000 ,   k ≤ 500 n\le50000,\ k\le 500 n50000, k500

斯特林数的一个应用,先考虑 O ( n k 2 ) O(nk^2) O(nk2)暴力怎么做,例如,求出x到其子树中所有点的距离k次之和(然后再转移出到所有点的答案即可,过程类似):
d p k [ x ] = ∑ y ∈ T r e e x d i s ( x , y ) k dp_k[x]=\sum_{y\in Tree_x} dis(x,y)^k dpk[x]=yTreexdis(x,y)k
= ∑ y ∈ s o n x ∑ z ∈ T r e e y ( d i s ( y , z ) + 1 ) k =\sum_{y\in son_x}\sum_{z\in Tree_y}(dis(y,z)+1)^k =ysonxzTreey(dis(y,z)+1)k
= ∑ y ∈ s o n x ∑ z ∈ T r e e y ∑ i = 0 k ( k i ) d i s ( y , z ) i =\sum_{y\in son_x}\sum_{z\in Tree_y}\sum_{i=0}^k \binom{k}{i}dis(y,z)^i =ysonxzTreeyi=0k(ik)dis(y,z)i
= ∑ y ∈ s o n x ∑ i = 0 k ( k i ) ∑ z ∈ T r e e y d i s ( y , z ) i =\sum_{y\in son_x}\sum_{i=0}^k\binom{k}{i}\sum_{z\in Tree_y}dis(y,z)^i =ysonxi=0k(ik)zTreeydis(y,z)i
= ∑ y ∈ s o n x ∑ i = 0 k ( k i ) d p i [ y ] =\sum_{y\in son_x}\sum_{i=0}^k\binom{k}{i}dp_i[y] =ysonxi=0k(ik)dpi[y]
按照上述结论转移即可做到 O ( n k 2 ) O(nk^2) O(nk2)
考虑优化,注意到:
n k = ∑ i = 1 k ( n i ) × S ( k , i ) × i ! n^k=\sum_{i=1}^k\binom{n}{i}\times S(k,i)\times i! nk=i=1k(in)×S(k,i)×i!
n k = ∑ i = 1 k S ( k , i ) × [ n ] i n^k=\sum_{i=1}^k S(k,i)\times [n]_i nk=i=1kS(k,i)×[n]i
(实际上i到底枚举到n还是k是不会影响答案的,但是这里k比较小,多枚举没有意义,因此只枚举到k)。其中S(n,k)为第二类斯特林数,表示把n个有标号球放到k个相同的非空盒子里的方案数,显然有:
S ( n , k ) = S ( n − 1 , k − 1 ) + S ( n − 1 , k ) × k S(n,k)=S(n-1,k-1)+S(n-1,k)\times k S(n,k)=S(n1,k1)+S(n1,k)×k
其意义是,要么第n个求单独一个盒子,要么和之前某个非空的盒子一组。
上一个式子的意义是,把k个带标号球放到n个带标号盒子里,显然有 n k n^k nk种方法,或者可以这么计数:先决定放到哪 1 ≤ i ≤ k 1\le i\le k 1ik个盒子里,然后用斯特林数放进去,但这样是不带标号盒子,所以要乘上一个阶乘。最后那个[n]_i叫做n的i次下降幂,其实就是 P ( n , i ) = n ( n − 1 ) ( n − 2 ) . . . ( n − i + 1 ) P(n,i)=n(n-1)(n-2)...(n-i+1) P(n,i)=n(n1)(n2)...(ni+1)。这告诉我们,对于k次幂计数,可以转化为对k次下降幂(其实就是组合数计数,二者只差一个阶乘)计数,然后用O(k)的时间转化回k次幂计数(事实上,用第一类斯特林数,也可以将下降幂计数转化成幂计数,再用O(k)的时间转化回去)。而组合数计数有一个好处:
( n k ) = ( n − 1 k ) + ( n − 1 k − 1 ) \binom{n}{k}=\binom{n-1}{k}+\binom{n-1}{k-1} (kn)=(kn1)+(k1n1)
把之前k次幂转化为组合数乘以斯特林数再乘上阶乘的式子代入,我们得到(略去化简):
d p k [ x ] = ∑ i = 1 k S ( k , i ) i ! ∑ y ∈ T r e e x ( d i s ( x , y ) i ) dp_k[x]=\sum_{i=1}^k S(k,i) i!\sum_{y\in Tree_x}\binom{dis(x,y)}{i} dpk[x]=i=1kS(k,i)i!yTreex(idis(x,y))
所以为题转化为,对后面一个sigma计数。
令(这里字母和代码中字母不同):
f k [ x ] = ∑ y ∈ T r e e x ( d i s ( x , y ) k ) = ∑ y ∈ s o n x ∑ z ∈ T r e e y ( d i s ( y , z ) + 1 k ) f_k[x]=\sum_{y\in Tree_x}\binom{dis(x,y)}{k}=\sum_{y\in son_x}\sum_{z\in Tree_y}\binom{dis(y,z)+1}{k} fk[x]=yTreex(kdis(x,y))=ysonxzTreey(kdis(y,z)+1)
注意这里的转化没有考虑dis(x,x),也就是当k=0的时候特殊处理一下。
应用上文提到的关于组合数的帕斯卡等式,有:
f k [ x ] = ∑ y ∈ s o n x ∑ z ∈ T r e e y [ ( d i s ( y , z ) k ) + ( d i s ( y , z ) k − 1 ) ] f_k[x]=\sum_{y\in son_x}\sum_{z\in Tree_y}\left[\binom{dis(y,z)}{k}+\binom{dis(y,z)}{k-1}\right] fk[x]=ysonxzTreey[(kdis(y,z))+(k1dis(y,z))]
f k [ x ] = ∑ y ∈ s o n x ( f k [ y ] + f k − 1 [ y ] ) f_k[x]=\sum_{y\in son_x}(f_k[y]+f_{k-1}[y]) fk[x]=ysonx(fk[y]+fk1[y])
因此这种转化的好处是,将递推的时间复杂度优化掉一个k,尽管还要再转化回去的复杂度会多一个k,但是对于 O ( n k 2 ) − O ( n ) O(nk^2)-O(n) O(nk2)O(n)的算法,改成 O ( n k ) − O ( n k ) O(nk)-O(nk) O(nk)O(nk)显然更优。
代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define lint long long
#define mod 10007
#define N 50010
#define K 510
using namespace std;
struct edges{
	int to,pre;
}e[N<<1];int fac[K],s[K][K],dp[N][K],ans[N][K],etop,h[N];
inline int add_edge(int u,int v)
{	return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop;	}
inline int prelude(int n)
{
	for(int i=fac[0]=1;i<=n;i++) fac[i]=(lint)fac[i-1]*i%mod;
	s[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			s[i][j]=(s[i-1][j-1]+j*s[i-1][j])%mod;
	return 0;
}
int get_dp(int x,int f,int k)
{
	memset(dp[x],0,sizeof(int)*(k+1)),dp[x][0]=1;
	for(int i=h[x],y;i;i=e[i].pre)
		if((y=e[i].to)^f)
		{
			get_dp(y,x,k),dp[x][0]+=dp[y][0];
			for(int j=1;j<=k;j++) dp[x][j]+=dp[y][j]+dp[y][j-1];
		}
	for(int i=1;i<=k;i++) (dp[x][i]>=mod?dp[x][i]%=mod:0);return 0;
}
inline int f(int x,int y,int i)
{	return ans[x][i]-dp[y][i]-(i>0?dp[y][i-1]:0)+mod*2;	}
int get_ans(int x,int fa,int k)
{
	for(int i=0;i<=k;i++) (ans[x][i]+=dp[x][i])%=mod;
	for(int i=h[x],y;i;i=e[i].pre)
		if((y=e[i].to)^fa)
		{
			ans[y][0]=ans[x][0]-dp[y][0];
			for(int j=1;j<=k;j++)
				ans[y][j]=f(x,y,j)+f(x,y,j-1);
			get_ans(y,x,k);
		}
	return 0;
}
inline int calc(int *ans,int k)
{
	lint res=0ll;
	for(int i=1;i<=k;i++) res+=(lint)fac[i]*s[k][i]*ans[i];
	return res%mod;
}
int main()
{
	int T;scanf("%d",&T),prelude(500);
	while(T--)
	{
		int n,k;scanf("%d%d",&n,&k);
		memset(h,0,sizeof(int)*(n+1)),etop=0;
		for(int i=1;i<n;i++)
		{
			int u,v;scanf("%d%d",&u,&v);
			add_edge(u,v),add_edge(v,u);
		}
		int rt=1;memset(ans[rt],0,sizeof(int)*(k+1));
		get_dp(rt,0,k),get_ans(rt,0,k);
		for(int i=1;i<=n;i++) printf("%d\n",calc(ans[i],k));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值