树形DP

1. 最大子树和

洛谷P1122

题目描述:
一株奇怪的花卉,上面共连有 N N N朵花,共有 N − 1 N-1 N1条枝干将花儿连在一起,并且未修剪时每朵花都不是孤立的。每朵花都有一个“美丽指数”,该数越大说明这朵花越漂亮,也有“美丽指数”为负数的。所谓“修剪”,意为:去掉其中的一条枝条,这样一株花就成了两株,扔掉其中一株。经过一系列“修剪”之后,还剩下最后一株花(也可能是一朵)。你的任务就是:通过一系列“修剪”(也可以什么“修剪”都不进行),使剩下的那株(或那朵)花卉上所有花朵的“美丽指数”之和最大。

输入格式
1.第一行一个整数 N ( 1 ≤ N ≤ 16000 ) N(1 ≤ N ≤ 16000) N(1N16000)。表示原始的那株花卉上共 N N N朵花。
2.第二行有 N N N个整数,第 i i i个整数表示第II朵花的美丽指数。
3.接下来 N − 1 N-1 N1行每行两个整数 a , b a,b a,b,表示存在一条连接第 a a a朵花和第 b b b朵花的枝条。
输出格式
一个数,表示一系列“修剪”之后所能得到的“美丽指数”之和的最大值。保证绝对值不超过 2147483647 2147483647 2147483647

由题意可知:
本题中之间以的连接可以简化为 N N N个点间 N − 1 N-1 N1条线先来表示。而每次修剪针对的对象是线而不是点。本题所求结果为最终可得的“美丽指数”和的最大值。于是本题可以使用树形DP来解决问题。
相当于从树的根节点依次向下,取不同的子节点。而由题意知当进行“修剪”时,被剪下的一整枝都要被丢弃,即从叶节点向上递归到的当前子节点要被抛弃。
由此得转移方程:
f [ u ] + = m a x ( f [ v ] , 0 ) f[u]+=max(f[v],0) f[u]+=max(f[v],0)
最终可得递归到根上的结果

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int r[1000002],cnt=0,h[100000],f[100000];
struct node{
	int to,next;
}edg[100000];
void add(int u,int to){
	++cnt;
	edg[cnt].to=to;
	edg[cnt].next=h[u];
	h[u]=cnt;
}
inline int qr(){
	int x=0,f1=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f1=-1;c=getchar();}
	while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
	return x*f1;
}
void dfs(int u,int fa){
	f[u]=r[u];
	for(int i=h[u];i;i=edg[i].next){
		int v=edg[i].to;
		if(v==fa)	continue;
		dfs(v,u);
		if(f[v]>=0) f[u]+=f[v];
	}
}
int main(){
	int n=qr();
	for(int i=1;i<=n;++i){
		r[i]=qr();
	}
	for(int i=1;i<n;++i){
		int l=qr(),k=qr();
		add(l,k);add(k,l);	//无向图
	}
	dfs(1,0);				//题意对根节点无特殊要求
	printf("%d",f[1]);
	return 0;
} 

总结与反思:

  1. 剪掉的子树上所连带有具有“漂亮值”的花,不能直接剪。
  2. 递归终止条件不要搞错

2.没有上司的舞会

洛谷P1352

题目描述
某大学有 n n n个职员,编号为 1 … n 1…n 1n。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 r i r_i ri ,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入格式
输入的第一行是一个整数 n n n
第二到第 ( n + 1 ) (n+1) (n+1)行,每行一个整数,第 ( i + 1 ) (i+1) (i+1)行的整数表示 i i i号职员的快乐指数 r i r_i ri
( n + 2 ) (n+2) (n+2)到第 2 n 2n 2n行,每行输入一对整数 l , k l, k l,k代表 k k k l l l的直接上司。
输出格式
输出一行共一个整数代表最大的快乐指数。

由题意可知:
本题以每个职员为节点,以职员与校长间的上下级关系为联系,所构成的一棵树.
且题意没有对树的有向或无向做任何规定,所以以无向处理.
再者当上司参加了舞会则下属一定不会参加.
然后输入数据中并未给出校长的标号.
可得树形背包: f [ u ] [ 0 ] + = w a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) ; f[u][0]+=wax(f[v][0],f[v][1]); f[u][0]+=wax(f[v][0],f[v][1]); f [ u ] [ 1 ] + = f [ v ] [ 0 ] ; f[u][1]+=f[v][0]; f[u][1]+=f[v][0];

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int r[100000],cnt=0,h[100000],f[100000][5];
struct node{
	int to,next;
}edg[100000];
inline int wax(int a,int b){
	return a>b?a:b; 
}
void add(int u,int to){
	++cnt;
	edg[cnt].to=to;
	edg[cnt].next=h[u];
	h[u]=cnt;
}
inline int qr(){
	int x=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
void dfs(int u,int fa){
	f[u][0]=0;
	f[u][1]=r[u];
	for(int i=h[u];i;i=edg[i].next){
		int v=edg[i].to;
		if(v==fa)	continue;
		dfs(v,u);
		f[u][0]+=wax(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];
	}
}
int main(){
	int n=qr(),rt,pre[100000];
	for(int i=1;i<=n;++i){
		r[i]=qr();
	}
	for(int i=1;i<n;++i){
		int l=qr(),k=qr();
		pre[l]=k;				//标记每个点的前驱
		add(l,k);add(k,l);
	}
	for(int i=1;i<=n;++i)		//通过前驱判断根节点(校长)编号
		if(!pre[i]) 
			rt=i;
	dfs(rt,0);
	int ans=wax(f[rt][0],f[rt][1]);
	printf("%d",ans);
	return 0;
} 

总结与反思

  1. 数组调太小
  2. 根节点无前驱

3.皇宫看守

信息学奥赛一本通 1579

题目描述
陆小凤成了皇上特聘的御前一品侍卫。皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状,某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

输入格式
输入中数据描述一棵树,描述如下:
第一行 n,表示树中结点的数目。第二行至第 n+1 行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 i(0<i≤n),在该宫殿安置侍卫所需的经费 k,该边的儿子数 m,接下来 m 个数,分别是这个节点的 m 个儿子的标号r1,r2,⋯,rm 。对于一个 n 个结点的树,结点标号在 1 到 n 之间,且标号不重复。
输出格式
输出最少的经费

由题意可知:
题目中的不同宫殿为节点,不同节点的守卫对应相对的宫殿。所以其在选点时共有三种不同状态需要记录。
f [ u ] [ 0 ] + = w i n ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) ; f[u][0]+=win(f[v][1],f[v][2]); f[u][0]+=win(f[v][1],f[v][2]); f [ u ] [ 1 ] + = w i n ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) ; f[u][1]+=win(f[v][1],f[v][2]); f[u][1]+=win(f[v][1],f[v][2]); f [ u ] [ 2 ] + = w i n ( w i n ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) , f [ v ] [ 2 ] ) ; f[u][2]+=win(win(f[v][0],f[v][1]),f[v][2]); f[u][2]+=win(win(f[v][0],f[v][1]),f[v][2]);
Code:

#include<iostream>
#include<cstdio>
#define INF 10e8 
using namespace std;
int cnt=0,h[10000],f[10000][30];
int k[10000];
struct node{
	int to;
	int next;
}edg[10000];
inline int qr(){
	int x=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
inline int wax(int a,int b){return a>b?a:b;}
inline int win(int a,int b){return a<b?a:b;}
void add(int u,int to){
	++cnt;
	edg[cnt].to=to;
//	edg[cnt].val=val;
	edg[cnt].next=h[u];
	h[u]=cnt;
}
void dfs(int u){
	int w=INF;
	for(int i=h[u];i;i=edg[i].next){
		int v=edg[i].to;
		dfs(v);
		f[u][0]+=win(f[v][1],f[v][2]);
		f[u][1]+=win(f[v][1],f[v][2]);
		w=win(w,f[v][2]-win(f[v][2],f[v][1]));
		f[u][2]+=win(win(f[v][0],f[v][1]),f[v][2]);
	}
	f[u][1]+=w;
	f[u][2]+=k[u];
}
int main(){
	int n=qr(),dgr[10000];
	for(int i=1;i<=n;++i){
		int nm=qr(),m;k[nm]=qr();m=qr();
		for(int j=1;j<=m;++j){
			int r=qr();
			dgr[r]=1;
			add(nm,r); 
		}
	} int rt; 
	for(int i=1;i<=n;++i){
		if(!dgr[i]){
			rt=i;
			break;
		}
	}
	dfs(rt);
	printf("%d",win(f[rt][1],f[rt][2]));
	return 0;
}

总结与反思

  1. 题目描述有问题,具有误导性内容(数据过大)
  2. 状态转移方程要进行验证,确保没有问题
  3. 一定要注意重复性较高代码分开写
  4. 不能忘掉递归终止条件
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值