0
点赞
收藏
分享

微信扫一扫

1576 例题2 选课(CTSC1997 LOJ10154 LUOGU2014 普及+/提高) 树形DP 树的深搜遍历,树的子节点数量求法,降维的01背包,无向图

大漠雪关山月 2022-02-16 阅读 32
动态规划

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

样例作图如下:

发现是两棵树。

每棵树都可以采用01背包处理,那树与树之间该如何拼接呢,继续01背包吗?

突然发现,样例数据可以进一步如下图处理:

 树根的序号定为0,多树就合并成了一棵树,将学分固定在树枝上。01背包就可以畅快的使用了。

具体算法如下:

sz[u]表示u的子树上的边数,可以通过计算u的子节点数量得到,请看样例中1的子节点数量是4,子树上的边数也是4。

设f[u][j]表示u的子树上保留j条边,至多保留的课程数目

那么状态转移方程也就显而易见了

f[u][i]=max(f[u][i],f[u][i-j-1]+f[v][j]+e[i].w)

f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w) ( 1≤j≤min(M,sz[u]),0≤k≤min(sz[v],j−1) )

u表示当前节点,v是u的一个子节点,sz[u]表示u的子树上的边数,M就是题目中要求的最多保留边数

那么为什么是这个方程呢?

首先,为什么是f[u][j-k-1]而不是f[u][j-k]?

为前文提到了,保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从u的子节点v的子树上留边的话,也要留下u,v之间的连边

那么取值范围k为什么要小于等于j−1而不是j呢?

同上,因为要保留u,v连边

对了,别忘了i,j要倒序枚举(数组少开了一维)因为这是01背包

更详细的01背包深入说明:点击这里

ybt

通过

测试点结果内存时间
测试点1答案正确640KB2MS
测试点2答案正确636KB2MS
测试点3答案正确632KB2MS
测试点4答案正确632KB1MS
测试点5答案正确632KB1MS
测试点6答案正确868KB2MS

LOJ

LUOGU

AC代码如下:

#include <bits/stdc++.h>
#define maxn 310
using namespace std;
int N,M;
struct node{
	int to,w,next;
}e[maxn<<1];
int head[maxn],tot,sz[maxn],f[maxn][maxn];//sz[]记录节点对应的子树节点数量 
void add(int u,int v,int w){//邻接表形式加入边 
	tot++,e[tot].to=v,e[tot].w=w,e[tot].next=head[u],head[u]=tot;
}
void init(){
	int u,v,w;
	scanf("%d%d",&N,&M);
	for(v=1;v<=N;v++){
		scanf("%d%d",&u,&w);
		add(u,v,w),add(v,u,w); 
	} 
}
void dfs(int u,int fa){
	int i,j,k,v;
	for(i=head[u];i;i=e[i].next){
		v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		sz[u]+=sz[v]+1;
		for(j=min(sz[u],M);j;j--)
			for(k=min(sz[v],j-1);k>=0;k--){
				f[u][j]=max(f[u][j],f[u][j-1-k]+f[v][k]+e[i].w);
			} 
	}
}
int main(){
	init();
	dfs(0,-1);
	printf("%d\n",f[0][M]);
	return 0;
}

该题收获:

自根向树叶节点遍历,自树叶节点向根节点开始做事。

树的子节点数量求法。

树上使用01背包。

自创数据对01背包进行模拟,更深入的了解了01背包空间上降维的原因。

 

举报

相关推荐

0 条评论