一般的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;
}