树形DP学习笔记

说实话,树形DP本不应该单独拿出来说,因为它本质就是一个动态规划。而且与一般的树形结构不同,树形DP不会去用临接表建图。这可能也是树形DP没有一个比较基础的教程的原因。

搞清楚了动态规划就搞清楚了树形DP

一道入门题也许可以让自己对树形dp有一个粗略的认识

luogu P1352
题目

题目描述

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

输入输出格式

输入格式:

第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0 0

输出格式:

输出最大的快乐指数。

输入输出样例

输入样例:

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0

输出样例:

5
分析

题中很明确的告知,学校里的职员关系像一颗树一样,以校长为根

于是,抽象出模型,就是一颗每个点都有权值的树

然后,题中给了一个条件,若某个节点u被选用,其子节点v都不可被选用

题中的问题是,要在满足上述条件的要求下,得到一个最大权值

由于每个节点只有选和不选两种状态,于是设f[u][1]指v号节点被选用后的最大值,f[u][0]是指v号节点没被选用时的最大值

对于选u号节点来说,u号节点的所有直接子节点v都不能选

对于不选u号节点来说,他的子节点v可选可不选

此时不难得到状态转移方程如下
f [ u ] [ 1 ] = s u m ( f [ v ] [ 0 ] ) + h a p p y [ u ] f[u][1]=sum(f[v][0])+happy[u] f[u][1]=sum(f[v][0])+happy[u]
f [ u ] [ 0 ] = s u m ( m a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) ) f[u][0]=sum(max(f[v][0],f[v][1])) f[u][0]=sum(max(f[v][0],f[v][1]))

分析状态转移方程可知,要知道节点u的状态,必须要知道其子节点v的状态,于是我们就有两种实现方法:从底层循环或直接dfs,这两种实现方法中dfs可能比较好打,但从底层循环不会有爆栈的风险

代码(dfs)
#include<bits/stdc++.h>
using namespace std;
#define frein(txtname); freopen(txtname,"r",stdin); 
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
int n,master;
const int maxn=6000+10;
int happy[maxn];
vector<int>follow[maxn];
bool vis[maxn];
int dp[maxn][2];
inline int read()
{
   int ans=0;bool negive=false;char r=getchar();
   while(r<'0'||r>'9'){if(r=='-')negive=true;r=getchar();}
   while(r>='0'&&r<='9'){ans=ans*10+r-'0';r=getchar();}
   return (negive)?-ans:ans;
}
void datasetting()
{
	n=read();
	loop(i,1,n)happy[i]=read();
	clean(vis,false);
	int k,l;l=read();k=read();
	while(l*k)
	{
		follow[k].push_back(l);
		vis[l]=true;
		l=read();k=read();
	}
	loop(i,1,n)if(!vis[i]){master=i;break;}
}
void dfs(int x)
{
	dp[x][0]=0;
	dp[x][1]=happy[x];
	int len=follow[x].size();
	loop(i,0,len-1)
	{
		int son=follow[x][i];
		dfs(son);
		dp[x][0]+=max(dp[son][1],dp[son][0]);
		dp[x][1]+=dp[son][0];
	}
}
int main()
{
   frein("datain.txt");
   datasetting();
   dfs(master);
   printf("%d",max(dp[master][0],dp[master][1]));
   return 0;
}
/********************************************************************
   ID:Andrew_82
   LANG:C++
   PROG:Tree shape dynamic programming
********************************************************************/

鄙人知识浅薄,望各位大佬斧正


update 2019.1.19
great!
再来几道题吧

1063: 士兵守卫

时间限制: 1 Sec 内存限制: 128 MB

题目描述

Bob特别喜欢战略游戏,但有时他不能尽快找到最优解,所以他就很伤心。现在他又有一个问题,他必须保卫一个中世纪的城市,这个城市的道路形成了一棵树。他需要在树的节点上放最少的士兵来观察所有的边。你能帮助他么?

例如下图就只需要一个士兵放在1号节点。

在这里插入图片描述
输入

输入文件soldier.in中有多组数据,每组数据的第一行N表示点的个数。接下来N行每行格式如下

x:(k) a1 a2 … ak(x为点的编号,k为与其相连的子节点个数,a1, a2, …, ak分别为子节点的编号)

输出

输出文件soldier.out,对于每组数据输出一行一个数,即最少士兵数。

样例输入

4
0:(1) 1
1:(2) 2 3
2:(0)
3:(0)
5
3:(3) 1 4 2
1:(1) 0
2:(0)
0:(0)
4:(0)

样例输出

1
2

code

#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) (a>b)?b:a
const int maxn=2000;
class treeDP
{
	private:
		int dp[maxn][2];
		int n;
		vector<int>edge[maxn];
		inline void addl(int u,int v);
		void calc(int u,int fa);
	public:
		void work();
}fun;
inline void treeDP::addl(int u,int v){edge[u].push_back(v);}
void treeDP::calc(int u,int fa)
{
	dp[u][0]=0;dp[u][1]=1;
	int len=edge[u].size();
	loop(i,0,len-1)
	{
		int v=edge[u][i];
		if(v==fa)continue;
		calc(v,u);
		dp[u][0]+=dp[v][1];
		dp[u][1]+=min(dp[v][0],dp[v][1]);
	}
}
void treeDP::work()
{
	freopen("datain.txt","r",stdin);
	while(scanf("%d",&fun.n)!=EOF)
	{
		loop(i,0,fun.n-1)fun.edge[i].clear();
		loop(i,1,fun.n)
		{
			int x,y,z;scanf("%d:(%d)",&x,&y);
			loop(j,1,y){scanf("%d",&z);fun.addl(x,z);fun.addl(z,x);}
		}
		fun.calc(0,-1);
		printf("%d\n",min(fun.dp[0][0],fun.dp[0][1]));
	}
	return;
}
int main()
{
	fun.work();
	return 0;
}
/**************************************************************  
Problem: DP    
User: Andrew82   
Language: C++    
****************************************************************/

题目传送门

1264: longest

题目描述

乌托邦有n个城市,某些城市之间有公路连接。任意两个城市都可以通过公路直接或者间接到达,并且任意两个城市之间有且仅有一条路径(What does this imply? A tree!)。

每条公路都有自己的长度,这些长度都是已经测量好的。

小修想从一个城市出发开车到另一个城市,并且她希望经过的公路总长度最长。请问她应该选择哪两个城市?这个最长的长度是多少?

Input format:

第一行n(n<=1000)。

以下n-1行每行三个整数a, b, c。表示城市a和城市b之间有公路直接连接,并且公路的长度是c(c<=10000)。

Output format:

仅一个数,即最长长度。

Sample:

longest.in

5
1 2 2
2 3 1
2 4 3
1 5 4

Longest.out

9

说明:从城市4到城市5,经过的路径是4-2-1-5,总长度是9。

code

#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
const int maxn=1010;
class DP
{
	public:
		struct node
		{
			int e;
			int dis;
			int nxt;
		}edge[maxn<<1];
		int head[maxn];
		int f[maxn][2];
		int n,cnt=0,ans=0;
		inline int read()
		{
			int ans=0;char r=getchar();
			while(r>'9'||r<'0')r=getchar();
			while(r<='9'&&r>='0')
			{ans=ans*10+r-'0';r=getchar();}
			return ans;
		}
		inline void addl(int u,int v,int w)
		{
			edge[cnt].dis=w;
			edge[cnt].e=v;
			edge[cnt].nxt=head[u];
			head[u]=cnt++;
		}
		void datasetting()
		{
			clean(head,-1);
			n=read();
			loop(i,1,n-1)
			{
				int a=read();int b=read();int c=read();
				addl(a,b,c);
				addl(b,a,c);
			}
		//clean(f,0);
		}
		void calc(int u,int fa)
		{
			f[u][0]=0;f[u][1]=0;
			for(int i=head[u];i!=-1;i=edge[i].nxt)
			{
				int v=edge[i].e;int w=edge[i].dis;
				if(v==fa)continue;
				//printf("%d-->%d\n",u,v);
				calc(v,u);
				int dis=f[v][0]+w;
				if(dis>f[u][0])
				{
					f[u][1]=f[u][0];
					f[u][0]=dis;
				}
				else f[u][1]=(dis>f[u][1])?dis:f[u][1];
				ans=(ans<f[u][1]+f[u][0])?f[u][1]+f[u][0]:ans;
			}
			return;
		}
		int Main()
		{
			datasetting();
			calc(1,0);
			printf("%d",ans);
		}
}fun;
int main()
{
	fun.Main();
	return 0;
}
/************************************************************** 
Problem: DP
User: mzg1802    
Language:C++
****************************************************************/

题目传送门

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值