0
点赞
收藏
分享

微信扫一扫

树形 DP

小云晓云 2022-02-08 阅读 69

树状DP

简单入门题

P1352 没有上司的舞会

一、dfs方法

普通dp一般用两个for循环,但是树形dp的基本操作就是将一个结点所有子树的信息合到这个节点上,我们的for循环显然就不能满足这个要求。我们采取dfs的方法。

#include <iostream>
#include <vector>
using namespace std;
const int MAX=6005;
int h[MAX]; 
int vis[MAX];
int dp[MAX][2];
vector<int> son[MAX];//son[x][i],值为x的节点的儿子们 
void dfs(int x){
	dp[x][0]=0;
	dp[x][1]=h[x];//x号职员去或不去的初始快乐值
	for(int i=0;i<son[x].size();i++){
		int s=son[x][i];
		dfs(s);
		dp[x][0]+=max(dp[s][0],dp[s][1]);//x不去,下属去或不去的最大值
		dp[x][1]+=dp[s][0]; 
	} 
	
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
	} 
	int x,y;//下属,上司 
	for(int i=1;i<=n-1;i++){//只有n-1个职员有公司 
		cin>>x>>y;
		son[y].push_back(x);
		vis[x]=1;//没有上司的是根节点 
	}
	int root;
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			root=i;
			break;
		}
	} 
	dfs(root);
	cout<<max(dp[root][0],dp[root][1]);
    return 0;
} 

更周到的解法

洛谷题解页面大佬提供的思路👇
如果我们的人数相当多且是一条链的时候就容易造成爆栈,那这我们有如何解决呢?
方法有三
1、开一个数组手动实现栈。
2、bfs后用for循环
3、拓扑排序

第一个想必大家都会写,而且其与dfs相似,所以不再赘述。
那为啥会讲后两种呢?
因为有时候dfs并不好写,所以我们会把它转化为bfs+for或者拓扑,大家可以看一下,dfs和这两种写法的推导有的是不一样的。特别是和这题的拓扑写法,可以仔细看一下。

二、bfs+for循环(终于有点理解为什么说dfs能做到的bfs都可以做到了)从底部节点开始,节点由孩子信息推出(叶子节点孩子信息为0哪)

我们很容易发现树形dp它为什么一般会是dfs形式?因为树形dp的状态大多是一颗颗子树,它传递状态过程一般都是先求出最下层再往上更新。所以对于每一个点,我们在求解它的值的过程中,需要求出它每一个子节点的解
那有什么方法我们可以用数组和for循环实现这样的求解呢?没错,就是bfs过程中的队列。由于队列中的点都是先入的父亲节点后入的子节点,所以我们求解的时候只要把循环顺序反过来就可以了。

从根节点自顶而下就需要dfs掌握所有子树信息
那么试想自底而上,先掌握底部节点的信息,可用bfs将节点依据深度依次放入队列,当然了,先进先出,先放的应该是底部节点

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int MAX=6005;
int h[MAX];
vector<int> son[MAX];
int fa[MAX];//以此判断根节点,感觉比vis是否有父亲难一点 
int dp[MAX][2];//dp[x][0]以x为根节点的子树上 在x上不妨士兵 共需士兵数
int vis[MAX];//节点是否入队的判断 
queue<int> Q;
int que[MAX];//到时候可逆序输出存储在队列中的节点
int cnt;//给que计数,到时候好逆序 
void bfs(int r){
	Q.push(r);
	que[cnt++]=r;
	vis[r]=1;
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		for(int i=0;i<son[x].size();i++){
			int s=son[x][i];
			if(!vis[s]){
			Q.push(s);
			que[cnt++]=s;
			vis[s]=1;
			}
		}
	}
} 
int main(){
	int n;
	cin>>n;
for(int i=1;i<=n;i++){
		cin>>h[i];
		fa[i]=i;
	} 
	int x,y;//下属,上司 
	for(int i=1;i<=n-1;i++){//只有n-1个职员有公司 
		cin>>x>>y;
		son[y].push_back(x);
		fa[x]=y; 
	}
	int r=n;//随便找了个节点r,寻找她的终极祖先 我们的根节点 
	while(r!=fa[r]){
		r=fa[r];//r现在是当初r的父亲 父亲的父亲的父亲直到根节点 
	}
	bfs(r);
	for(int i=cnt-1;i>=0;i--){
		int x=que[i];
		for(int j=0;j<son[x].size();j++){
			int s=son[x][j];
			dp[x][0]+=max(dp[s][0],dp[s][1]);
			dp[x][1]+=dp[s][0];
		}
		dp[x][1]+=h[x];
	}
	cout<<max(dp[r][0],dp[r][1]);
    return 0;
} 

三、拓扑排序(节点fa[x]由节点x信息推出)

先回忆一下拓扑排序
在这里插入图片描述

拓扑排序,先将所有入度为0的点入队,列举队列中元素,逐一出队,出队时改变邻接结点的入度,如果邻接结点入读减为0就将其入队
现在从后往前,就看出度是否为0
这次就是真真切切的先存底部叶子节点再依次往上了

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int MAX=6005;
int h[MAX];
vector<int> son[MAX];
int fa[MAX];//以此判断根节点,感觉比vis是否有父亲难一点 
int dp[MAX][2];//dp[x][0]以x为根节点的子树上 在x上不妨士兵 共需士兵数
int vis[MAX];//节点是否入队的判断 
queue<int> Q;
int degree[MAX];
int main(){
	int n;
	cin>>n;
for(int i=1;i<=n;i++){
		cin>>h[i];
	} 
	int x,y;//下属,上司 
	for(int i=1;i<=n-1;i++){//只有n-1个职员有公司 
		cin>>x>>y;
		son[y].push_back(x);
		fa[x]=y; 
		degree[y]++;//出度为0的点(没有孩子的点) 
	}
	for(int i=1;i<=n;i++){
		if(degree[i]==0)Q.push(i);
	} 
	int maxx=0;
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		dp[x][1]+=h[x];
		maxx=max(maxx,dp[x][1]);
		maxx=max(maxx,dp[x][0]);
		degree[fa[x]]--;
		if(degree[fa[x]]==0)Q.push(fa[x]);
		dp[fa[x]][1]+=dp[x][0];
		dp[fa[x]][0]+=max(dp[x][0],dp[x][1]);
	}
	cout<<maxx;
    return 0;
} 

P2016 战略游戏

#include <iostream>
#include <vector>
using namespace std;
const int MAX=1500;
vector<int> son[MAX];
int vis[MAX];
int dp[MAX][2];//dp[x][0]以x为根节点的子树上 在x上不妨士兵 共需士兵数
void dfs(int x){
	dp[x][1]=1;
	dp[x][0]=0;//不放就只花0个士兵
//	if(son[x].size()==0)return;
	for(int i=0;i<son[x].size();i++){
		int s=son[x][i];
		dfs(s);
		dp[x][1]+=min(dp[s][1],dp[s][0]);
		dp[x][0]+=dp[s][1];
	} 
}
int main(){
	int n;
	cin>>n;
	int x,y,k;//x节点有k个节点与他相连(k个孩子 
	for(int i=0;i<n;i++){
		cin>>x>>k;
		for(int i=0;i<k;i++){
			cin>>y;
			son[x].push_back(y);
			vis[y]=1;
		} 
	}
	int root;
	for(int i=0;i<n;i++){
		if(!vis[i]){
			root=i;
			break;
		}
	}
	dfs(root);
	cout<<min(dp[root][1],dp[root][0]);
    return 0;
} 
举报

相关推荐

树形DP.

树形dp总结

树形dp 笔记

树形DP练习

树形DP专题

Weight the Tree(树形dp)

题解 树形DP (一)

0 条评论