树形DP(HDU1520)

一般的DP都是在线性结构上的DP,对于树形结构也是可以进行DP的。在树上的DP叫做树形DP,树形DP其实可以类比一般的DP,一般的线性DP的其实也有两种:人人为我的DP与我为人人的DP。这个具体的含义我们接下来再说,其实无非就是DP的两种方向,一种是从起点到终点开始DP,一种是从终点向起点DP。在树形结构中其实也有这种关系,树形结构相连的节点的关系是一种父子关系,所以DP的也会分为两种,从父节点转移到子节点,从子节点转移到父节点。来看一个树形DP的一个经典的入门题。

参加party。这个问题的大意就是一群人要参加一个party,每个人都有一个开心值,这些人有严格的上下级关系,当莫个人参加的时候他的直接上级就不能参加,否则他就会不开心(哈哈,挺有意思的心态)。给出这些人的上下级关系,问你该如何安排才能使得所有人的开心值加起来最大。

分析:首先我们可以知道这些上下级的关系就是一个树,规则被映射到树上就是说父子节点不能同时被选中。我们采用一个二维的数组f[maxn][2]这个数组是一个二维的数组,第一维代表当前节点,第二维代表是否参加payty。0表示不参加,1表示参加。那么我们当前节点的值我们可以通过这个节点的子节点转移过来。不难分析:f[u][0]=sum(max(f[v][1],f[v]0)),f[u][1]=sum(f[v][0]);以上就是这个状态在树上的转移方程,我们来解释一下这个方程的含义。首先u是当前节点,当前节点不被选中的时候,那么这个节点的最大值应该是v(u的子节点)的最大值,v的最大值有两种情况,要么被选中,要么不被选中。所以二者取最大。如果这个节点被选中,那么这个节点的值就是子节点不被选中的时候对应的状态。sum是求和。转移方程搞定之后其实就已经完成一大半了,之后就是建树:建树我们采用二维的数组模拟这个树,开一个vector的数组,每一个加入两个点,同时我们需要一个in数组,这个数组将要记录每一个点的入度,这是很必要的,因为在输入的时候我们没办法直接判断到底谁是根节点,所以我们记录所有节点的入度,到时候只要遍历整个数组找到入度为0的节点,从这里开始DFS即可。DFS要做的事情其实也是简单了,在一般的线性DP中,我们需要遍历整个数组,来更新整个DP数组,在树形结构中我们跑一个DFS遍历整个数组,只是不同的遍历方式,其实思想都是一样的。OK,但是还是要注意一些细节的问题的实现。数组的下标什么的,这里的细节需要处理好。具体代码实现如下。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 6e3 + 10;
vector<int>vec[maxn];
int vis[maxn];
int f[maxn][2];
int in[maxn];
int value[maxn];
void dfs(int u)
{
	//首先来初始化节点
	f[u][0] = 0;
	f[u][1] = value[u];
	vis[u] = 1;
	for (int i = 0; i < vec[u].size(); i++) {
		int v = vec[u][i];
		if (vis[v])continue;
		dfs(v);
		f[u][0] += max(f[v][0], f[v][1]);
		f[u][1] += f[v][0];
	}
	return;
}
int main()
{
	int n, u, v, i, j, k;
	while (cin >> n && n)
	{
		for (int i = 0; i <= n; i++)vec[i].clear();
		for (int i = 1; i <= n; i++)cin >> value[i];
		memset(in, 0, sizeof(in));
		while (cin >> v >> u && (v + u))
		{
			vec[u].push_back(v);
			in[v]++;
		}
		for (int i = 1; i <= n; i++)
		{
			if (!in[i])
			{
				memset(vis, 0, sizeof(vis));
				memset(f, 0, sizeof(f));
				dfs(i);
				cout << max(f[i][0], f[i][1]) << endl;
				break;
			}
		}
	}
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41863129/article/details/83184546