【题解】codeforces1097G. Vladislav and a Great Legend 树形dp+计数

22 篇文章 0 订阅
20 篇文章 0 订阅
本文介绍了如何利用树形动态规划(DP)解决codeforces1097G题目的一个算法,该题目涉及计算集合的权值F(X)的k次方之和。通过将k次方转化为选择k维向量,再进行有序不重复统计,结合树形DP进行状态转移,最终推导出复杂度为O(n * k)的解决方案。强调了清晰的逻辑和式子推导在解题过程中的重要性。
摘要由CSDN通过智能技术生成

题目连接

题意

给定一棵树,选择一个点集合X,权值F(X)= 联通该集合边的条数
求对所有点集合X,sigma(F(X) k)
n <= 1e5 , k <= 200

题解

一开始以为用斯特林数维护k次方。但是不太会转化
题解给出了一个巧妙的转化:
把k次方看成选一个k维向量,统计每个向量的出现次数。
为了统计方便,先统计有序且不重复出现的
i维向量(1<=i<=k),然后简单dp可以求出把i维向量选成k维的方案数

树形dp的时候要把状态间的转移理清楚。
应该从要求的东西开始,一步步理清条理还是很显然的。但是我推的时候一开始没有想清楚,推了很久
用F(i,j,0…2)表示在子树i,选了j维向量,当前根被选/未选/未选但作为lca的方案数。转移的时候只需要注意第一次选择点的时候贡献是2sz[i]-1,其他直接乘2就好。把边放到它下面的点(其实我一开始把题看错了,看成点的贡献,不过这样转化很方便)

关于复杂度,for的时候与sz取min是O(n * k)的。我们考虑每次小的子树合并给大的,花费k^2的时间size增加k。复杂度 = n / k * k , 而小的子树本身复杂度不超过k ^ 2
这和n ^ 3优化成n ^ 2的树形dp思路一样!

总结:
推式子的题要理清逻辑,一步步把式子推清楚,快速的思考,思维要清晰!可以边写边推,这样对式子更有概念(但是每个部分写之前要推完)
尽量1次AC,只要式子正确完全可以做到
还有这题开longlong恰好mle,细节还是要注意

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const ll inf = 2e18;
const int N = 3e4 + 10;
const int maxn = 100020;
const ll mod = 1e9 + 7;

struct node{
	int next,to;
}e[maxn * 2];
int head[maxn],cnt,sz[maxn];
int n,k;

ll inv[maxn],fac[maxn],dp[220][220],sum[220],pow2[maxn];
int f[maxn][220][3],g[220][3];

inline ll power(ll x,ll y){
	y = ((y % (mod - 1)) + (mod - 1)) % (mod - 1);
//	if ( y < 0 ) return power(power(x,-y),mod - 2);
	ll res = 1;
	while ( y ){
		if ( y & 1 ) res = res * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return res;
}
inline void up(ll &x,ll y) { x = (x + y) % mod; }
inline void up(int &x,ll y) { x = (x + y) % mod; }
void init(){
	inv[0] = fac[0] = 1;
	rep(i,1,200) fac[i] = fac[i - 1] * i % mod , inv[i] = power(fac[i],mod - 2);
	dp[0][0] = 1;
	rep(i,1,k){
		rep(j,1,k){
			rep(num,1,j){
				up(dp[i][j],dp[i - 1][j - num] * inv[num]);
			}
		}
	}
	pow2[0] = 1;
	rep(i,1,n) pow2[i] = pow2[i - 1] * 2 % mod;
}
void dfs(int x,int fa){
	f[x][0][0] = 1;
	fore(i,x){
		if ( e[i].to == fa ) continue;
		dfs(e[i].to,x);
		memcpy(g,f[x],sizeof(g));
		memset(f[x],0,sizeof(g));
		rep(j,0,min(k,sz[x])) rep(l,0,min(k,sz[e[i].to])){
			// 0 : x isn't in the set
			// 1 : x is chosen
			// 2 : x isn't chosen but is lca
			if ( j + l > k ) break;
			if ( !j || !l ){
			   	up(f[x][j + l][0],g[j][0] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
				up(f[x][j + l][2],g[j][2] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));	
			}
			else{
				up(f[x][j + l][2],g[j][0] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
				up(f[x][j + l][2],g[j][2] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
			}
		}
		sz[x] += sz[e[i].to];
	}
	sz[x]++;
	up(f[x][1][1],pow2[sz[x]] - 1); //第一次选点的时候统计子树内贡献
	//f[x][0][0] = f[x][0][2] = 0;
	rep(i,0,min(k,sz[x])){
	   	if ( i ) up(f[x][i + 1][1],2ll * (f[x][i][0] + f[x][i][2]));
		f[x][i][0] = f[x][i][0] * 2 % mod;
		f[x][i][2] = f[x][i][2] * 2 % mod;
	}
}
void solve(){
	ll ans = 0;
	rep(i,1,k){
	//	cout<<"i: "<<i<<endl;
		if ( i == 1 ){
			rep(j,1,n)	up(sum[i],(pow2[n - sz[j]] - 1) * (pow2[sz[j]] - 1));
		}
		else{
			rep(j,1,n){
			   	up(sum[i],f[j][i][2] * pow2[n - sz[j]] + f[j][i][1] * (pow2[n - sz[j]] - 1));
		//		cout<<f[j][i][2]<<" "<<f[j][i][1]<<endl;
			}
		}
		up(ans,sum[i] * dp[i][k] % mod * fac[k]);
	}
	cout<<ans<<endl;
}
inline void adde(int x,int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
int main(){
	scanf("%d %d",&n,&k);
	rep(i,1,n - 1){
		int x,y;
		scanf("%d %d",&x,&y);
		adde(x,y) , adde(y,x);
	}
	init();
	dfs(1,0);
	solve();
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值