1575 例题1 二叉苹果树(LOJ10153 LUOGU2015 普及+/提高) 树形DP 不能用有向图原因 树的深搜遍历,树的子节点数量求法,降维的01背包,无向图

  • Post author:
  • Post category:其他



总目录


在线测评地址(ybt)


在线测评地址(LOJ)


在线测评地址(LUOGU)


这是一篇写得比较久的文章,前前后后三个星期。

该题的输入数据描述比较绕,反复读了几次,才明白,每行的数据描述的是一根树枝的信息,

每行的前两个数据,代表该数值的端点,第三个数据,代表该树枝上的苹果数量。

可以将苹果挂在树的节点上,这样题意更清晰,样例对应的图如下:

从暴力角度想了想,如下情况苹果树,先删那根枝呢?

确实难办,该题还是有特定算法的。

还是回归本题的表述吧。

树的拼接。

f[2][0]=0,f[5][0]=0,f[4][0]=0
f[3][0]=0,f[3][1]=max(f[3][0]+f[2][0]+20,f[3][0]+f[5][0]+20)=max(20,20)=20
f[3][2]=max(f[3][1]+f[2][0]+20,f[3][1]+f[5][0]+20)=40//f[3][1] 是确定的,那f[3][2]也就是确定的,还需要枚举?
枚举挺复杂的
枝的选取是有序的,被选择的枝,下一次是不会再被选择了,这也是01背包算法的特征。

模拟是理解算法的关键。

对01背包空间降维,逆向处理有问题,可看,若无问题,可跳过这段。

这部分是关于01背包的数据生成过程,若对01背包有疑惑,读者可以独自进行模拟,以下过程仅代表作者的模拟过程,不要求读者看懂。


更详细的01背包深入说明:

点击这里

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(q,sz[u]),0≤k≤min(sz[v],j−1) )

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

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

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

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

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

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

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

考虑用有向图应也能编写成功。在编写有向图中,不成功,重新看了看输入数据,发现,给出的数

请注意,如下的输入数据1与输入数据2描述的是同一棵树,
这两组数据最大的差别在于
2 3 20

3 2 20
正式因为这种差别,该题不能用有向图来存储数据。

输入数据1:
5 2
1 3 1
1 4 10
2 3 20
3 5 18
输出数据1:
21

输入数据2:
5 2
1 3 1
1 4 10
3 2 20
3 5 18
输出数据2:
21

据,并没有严格的指向叶节点,或叶节点指向某个点,故

有向图编写无法成功


1.


树的深搜遍历(深搜到叶节点,返回根节点),树的子节点数量求法(树的子树边数求法),降维的01背包,无向图。


ybt


通过

测试点 结果 内存 时间
测试点1
答案正确
616KB 1MS
测试点2
答案正确
628KB 2MS
测试点3
答案正确
628KB 1MS
测试点4
答案正确
648KB 1MS
测试点5
答案正确
660KB 2MS
测试点6
答案正确
660KB 2MS
测试点7
答案正确
664KB 1MS
测试点8
答案正确
676KB 1MS
测试点9
答案正确
680KB 2MS
测试点10
答案正确
704KB 2MS
测试点11
答案正确
696KB 1MS
测试点12
答案正确
696KB 2MS
测试点13
答案正确
704KB 2MS


LOJ


LUOGU



AC代码如下:

#include <bits/stdc++.h>
#define maxn 110
using namespace std;
int f[maxn][maxn];
struct node{
	int to,w,next;//to表示终点,w表示权重,next表示下一条边 
}e[maxn<<1];
int head[maxn],tot,sz[maxn],q;//sz[u]统计以u为父节点的树枝数量 
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 dfs(int u,int fa){//fa父节点,u子节点 
	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;//u有分支,故用sz[u]+;+1表示u,v之间的这根树枝
		for(j=min(sz[u],q);j;j--)
			for(k=min(sz[v],j-1);k>=0;k--){//注意此处条件是k>=0 
				f[u][j]=max(f[u][j],f[u][j-1-k]+f[v][k]+e[i].w);
			} 
	}
}
int main(){
	int n,i,u,v,w;
	scanf("%d%d",&n,&q);
	for(i=1;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w),add(v,u,w);//无向图 
	}
	dfs(1,-1);//从根搜起 
	printf("%d\n",f[1][q]);
	return 0;
} 
 

该题收获:

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

树的子节点数量求法。

树上使用01背包。

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



版权声明:本文为mrcrack原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。