[luogu8352] [SDOI2022 d2t1] 小N的独立集 - 计数 - dp套dp

本文详细介绍了如何解决树上最大权独立集的计数问题,通过dp套dp的方法优化状态,将状态数从O(nk^2)降低到O(n^2k^2),并给出了转移方程,最终实现O(n^2k^4)的时间复杂度解决方案。文章适合对算法和数据结构有一定基础的读者,深入理解树上背包的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门:P8352 [SDOI/SXOI2022] 小 N 的独立集
题目大意:给定一棵 n n n 个点的树,每个点的权值在 1 ∼ k 1\sim k 1k 之间,问有多少种权值的方案使得整棵树的最大权独立集大小为 m m m ,对 m = 1 ∼ n k m=1\sim nk m=1nk 分别求解。
因为是全场最简单(果然以我的水平只能出得出来这种送分题了吗),废话不多说直接上题解。
我们都知道树上最大权独立集的求法: f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1] 表示以 x x x 为根的子树,根是否被选择的答案。
而这里的问题是计数,因此需要dp套dp:设 g [ x ] [ p ] [ q ] g[x][p][q] g[x][p][q] 表示以 x x x 为根的子树, f [ x ] [ 0 ] = p f[x][0]=p f[x][0]=p f [ x ] [ 1 ] = q f[x][1] =q f[x][1]=q 的方案数。
当然这样状态数直接爆炸了: p p p q q q 都是 O ( n k ) O(nk) O(nk) 级别,状态数直接达到立方。我们先优化状态。
注意到最大权独立集的dp其实还有另一种写法: f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1] 表示以 x x x 为根的子树, 根是否必须不选 的答案。容易观察到必然有 f [ x ] [ 1 ] ≤ f [ x ] [ 0 ] ≤ f [ x ] [ 1 ] + k f[x][1] \leq f[x][0] \leq f[x][1] + k f[x][1]f[x][0]f[x][1]+k ,这是因为限制根必须不选肯定没有不加以限制来得优秀,同时即使选了根也最多会让你的答案增加 k k k
那么再设计 g g g 的时候,就没有必要把 p p p q q q 都开满了:设 g [ x ] [ p ] [ q ] g[x][p][q] g[x][p][q] 表示 x x x 的子树, f [ x ] [ 1 ] = p f[x][1]=p f[x][1]=p f [ x ] [ 0 ] = p + q f[x][0]=p+q f[x][0]=p+q 的答案,那么 q q q 一定在 0 ∼ k 0 \sim k 0k 中取值。状态数降到了 O ( n 2 k 2 ) O(n^2k^2) O(n2k2)
转移也比较简单:大力枚举dp值,设 y y y x x x 的子节点,则 f [ x ] [ p ] [ q ] × f [ y ] [ p ′ ] [ q ′ ] f[x][p][q] × f[y][p'][q'] f[x][p][q]×f[y][p][q] 可以转移到 f [ x ] [ p + p ′ + q ′ ] [ m a x ( 0 , q − q ′ ) ] f[x][p+p'+q'][max(0,q-q')] f[x][p+p+q][max(0,qq)] 。这是因为如果强制不选 x x x ,则可以选 y y y ,对应的答案是 p + p ′ + q ′ p+p'+q' p+p+q ;另一种选择是不选 y y y ,那么此时可以选 x x x ,对应的答案是 p + p ′ + q p+p'+q p+p+q
基于树上背包的复杂度分析,可得总的时间复杂度为 O ( n 2 k 4 ) O(n^2k^4) O(n2k4)
(彩蛋:小N和小 Ω \Omega Ω 其实是小Z和小C旋转90度。)

#include<bits/stdc++.h>
using namespace std;
int n,k;
struct edge{
	int to,nxt;
}e[2010];
int cnt,fir[1010];
inline void ins(int u,int v){
	e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;
	e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;
}
int sz[1010];
const int mo = 1000000007;
long long f[1010][5010][6],p[5010][6],ans[5010];
void dfs(int x,int fa){
	int i,j,l,g,h;
	for(i = 1;i <= k;++i) f[x][0][i] = 1;
	sz[x] = k;
	for(i = fir[x];i;i = e[i].nxt) if(e[i].to != fa){
		int y = e[i].to;
		dfs(y,x);
		memset(p,0,sizeof(p));
		for(j = 0;j <= sz[x];++j){
			for(l = 0;l <= k;++l) if(f[x][j][l]){
				for(g = 0;g <= sz[y];++g){
					for(h = 0;h <= k;++h) if(f[y][g][h]){
						(p[j + g + h][max(0,l - h)] += f[x][j][l] * f[y][g][h]) %= mo;
					}
				}
			}
		}
		memcpy(f[x],p,sizeof(p));
		sz[x] += sz[y];
	}
}
int main(){
	int i,j,u,v;
	scanf("%d%d",&n,&k);
	for(i = 1;i < n;++i){
		scanf("%d%d",&u,&v);
		ins(u,v);
	}
	dfs(1,0);
	for(i = 1;i <= n * k;++i){
		for(j = 0;j <= i && j <= k;++j) ans[i] += f[1][i - j][j];
		printf("%lld\n",ans[i] % mo);
	}
	return 0;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值