【题解】[SDOI/SXOI2022] 小 N 的独立集(dp of dp)

题目分析:

就借助这个题稍微说一下 dpdp
对于 dpdp 其解决的问题是:若给定某一具体情况则答案十分好求,现要求对于所有的情况的答案进行统计。
这类问题我们一般称解决这个具体情况的 dp 为内层 dp,而对于所有情况进行统计的 dp 为外层 dp
一般的解决方法是:将内层 dp 的状态直接扔到外层 dp 的下标上,然后再按需要加入一些新的下标,也就是说假设内层 dp 的状态为 f1,f2,f3,那么我们的外层 dp 则至少应为 gx,y,z,表示 f1=x,f2=y,f3=z 时统计的答案,转移时即按照内层 dp 的转移方式对于下标进行转移。
下面就以这个题为例看看 dpdp 的具体应用。

看到这个题的第一眼就可以知道,若给定权值,则树上的最大权独立集是很好求的,就是直接设 fu,0/1 表示以 u 为根的子树,u 节点不选/选的最大权独立集是多少。
考虑 f 的值并不大,所以可以使用 dpdp,即设 gu,i,j 表示以 u 为根的子树 fu,0=i,fu,1=j 的方案数,那么转移就是显然的:(设 vu 的儿子节点)

gu,i,j×gv,x,ygu,i+max(x,y),j+y

根据树上背包的复杂度分析,这样直接做的复杂度为 O(n4k4)
对于 dpdp 常见优化就是压缩状态或者优化转移。
我们其实可以发现一条性质:fu,1 不会比 fu,0 大很多,虽然 fu,0 可能比 fu,1 大很多。
也就是说 max(fu,1,fu,0)fu,0 不大,稍微分析一下就可以发现满足:0max(fu,0,fu,1)fu,0valu=k
因为 fu,0 是在子树上按最优的方式转移的,即使 fu,1 也是最优的方式转移也只会多 valu 的贡献。
(注意这一行状态已经重新设计了)所以我们就可以将内层 dp 状态重新设计为:fu,0/1 表示 u 节点不强制/强制不选的答案,gu,i,j 表示 fu,1=i,fu,0=fu,1+j 的方案数。
转移也是显然的:

gu,a,b×gv,c,dgu,a+c+d,max(a+c+d,a+b+c)(a+c+d)

这样的话复杂度就优化为了 O(n2k4)
需要注意一点就是 gu,i,jj 可以为 0,因为显然不强制不选的最优答案可以为不选。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3+5;
const int K = 7;
const int MOD = 1e9+7;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int n,k,cnt,head[N],sz[N],g[N][N*K][K],f[N*K][K],ans[N*K];
//g[now][i][j] 表示 now 为根的子树,now 随意选不选权值为 i + j,now 强制不选权值为 i 的方案数 
void add_edge(int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
void dfs(int now,int fath){
	sz[now] = 1;
	for(int i=1; i<=k; i++)	g[now][0][i] = 1;
	for(int i=head[now]; i;i=e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);memset(f,0,sizeof(f));
		for(int a=0; a<=sz[now]*k; a++){
			for(int b=0; b<=k; b++){
				if(g[now][a][b] == 0)	continue;
				for(int c=0; c<=sz[to]*k; c++){
					for(int d=0; d<=k; d++){
						f[a+c+d][max(a+b+c,a+c+d)-(a+c+d)] = (f[a+c+d][max(a+b+c,a+c+d)-(a+c+d)] + g[now][a][b] * g[to][c][d])%MOD;
						//其实转移就是正常的转移 
					}
				}
			}
		}
		memcpy(g[now],f,sizeof(f));
		sz[now] += sz[to];
	}
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%lld%lld",&n,&k);
	for(int i=1; i<n; i++){
		int from,to;scanf("%lld%lld",&from,&to);
		add_edge(from,to);add_edge(to,from);
	}
	dfs(1,0);
	for(int i=1; i<=n*k; i++){
		for(int j=0; j<=min(i,k); j++){
			ans[i] = (ans[i] + g[1][i-j][j])%MOD;
		}
	}
	for(int i=1; i<=n*k; i++)	printf("%lld\n",ans[i]);
	return 0;
}
posted @   linyihdfj  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示