洛谷 P4827 [国家集训队] Crash 的文明世界(第二类斯特林数 + 树形DP + 思维)

在这里插入图片描述


用第二类斯特林公式代入 d i s t ( i , j ) k dist(i,j)^k dist(i,j)k(接下来用 d j d_j dj表示dis(i,j)): S ( i ) = ∑ j = 1 n d j k S(i) = \sum_{j = 1}^nd_j^k S(i)=j=1ndjk = ∑ j = 1 n ∑ t = 0 k S ( k , t ) [ d j ] t =\sum_{j = 1}^n\sum_{t=0}^kS(k,t)[d_j]_t =j=1nt=0kS(k,t)[dj]t = ∑ j = 1 n ∑ t = 0 k S ( k , t ) C ( d j , t ) ∗ t ! =\sum_{j = 1}^n\sum_{t=0}^kS(k,t)C(d_j,t)*t! =j=1nt=0kS(k,t)C(dj,t)t! = ∑ t = 0 k S ( k , t ) ∗ t ! ∑ j = 1 n C ( d j , t ) =\sum_{t=0}^kS(k,t)*t!\sum_{j = 1}^nC(d_j,t) =t=0kS(k,t)t!j=1nC(dj,t)

变成要快速求解 ∑ j = 1 n C ( d j , t ) \sum_{j = 1}^nC(d_j,t) j=1nC(dj,t),接下来一步比较巧妙。
由于暴力是必定要T的,应该考虑是否能从其它点的答案转移以缩小时间复杂度。

对于二项式有个递推式: ∑ j = 1 n C ( d j , t ) = ∑ j = 1 n C ( d j − 1 , t ) + ∑ j = 1 n C ( d j − 1 , t − 1 ) \sum_{j = 1}^nC(d_j,t) = \sum_{j = 1}^nC(d_j - 1,t) + \sum_{j = 1}^nC(d_j - 1,t - 1) j=1nC(dj,t)=j=1nC(dj1,t)+j=1nC(dj1,t1)

满足 d j − 1 d_j - 1 dj1的点一定是当前点的子结点或父节点,这意味着可以树形DP,用两次扫描换根来转移。
d p [ i ] [ t ] dp[i][t] dp[i][t]表示结点 i i i ∑ j = 1 n C ( d j , t ) \sum_{j = 1}^nC(d_j,t) j=1nC(dj,t), d p [ u ] [ t ] = d p [ v ] [ t ] + d p [ v ] [ t − 1 ] , v = s o n ( u ) ∣ f a t h e r ( u ) dp[u][t] = dp[v][t] + dp[v][t-1],v =son(u) | father(u) dp[u][t]=dp[v][t]+dp[v][t1],v=son(u)father(u)
复杂度为 O ( n k ) O(nk) O(nk)


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 100;
const int mod = 10007;
int n,k;
vector<int> g[maxn];
int S[300][300],dp[maxn][300],pw[maxn];
int cur[300];
void dfs(int u,int f) {
	dp[u][0] = 1;
	for(auto it : g[u]) {
		if(it == f) continue;
		dfs(it,u);
		dp[u][0] += dp[it][0];
		dp[u][0] %= mod;
		for(int j = 1; j <= k; j++) {
			dp[u][j] += (dp[it][j - 1] + dp[it][j]) % mod;
			dp[u][j] %= mod;
		}
	}
}
void dfs2(int u,int f) {
	for(auto it : g[u]) {
		if(it == f) continue;
		cur[0] = (dp[u][0] - dp[it][0] + mod) % mod;
		for(int j = 1; j <= k; j++) {
			cur[j] = dp[u][j] - dp[it][j] - dp[it][j - 1];
			cur[j] = (cur[j] + mod + mod) % mod;
		}
		dp[it][0] = (dp[it][0] + cur[0]) % mod;
		for(int j = 1; j <= k; j++) {
			dp[it][j] += (cur[j] + cur[j - 1]) % mod;
			dp[it][j] %= mod;
		}
		dfs2(it,u);
	}
}
int main() {
	scanf("%d%d",&n,&k);
	for(int i = 1; i < n; i++) {
		int u,v;scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	pw[0] = S[0][0] = 1;
	for(int i = 1; i <= 300; i++) {
		pw[i] = pw[i - 1] * i % mod;
		for(int j = 1; j <= i; j++)
			S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j] % mod) % mod;
	}
	dfs(1,0);dfs2(1,0);
	/*for(int i = 1; i <= n; i++)
		for(int j = 1; j <= k; j++)
			printf("**%d\n",dp[i][j]);*/
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		ans = 0;
		for(int j = 0; j <= k; j++) {
			ans += dp[i][j] * S[k][j] % mod * pw[j] % mod;
			ans %= mod;
		}
		printf("%d\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值