前言

由于能力限制,本文只介绍树形 DP 这个分类。

前置芝士:存图

不要以为是树就不用存图了

说道存图嘛,哎嘿嘿

链式前向星

没错又是它(阴魂不散)

这玩意已经介绍 \(n\) 遍了,这里就简单提一嘴。

这里的链式前向星相较于图论那块的少了 \(dis\),因为这个边权没有用,其他的按照原来照搬就行。

正文

到了我们的树形 DP 了,一般的 DP 例如背包,区间,都是线形 DP,与树形 DP 不一样的是,线形 DP 题目一般不会给前驱和后继(一般都是按下标决定),而树形呢,你不存图试试,所以,树形 DP 就可以简化成线形 DP 加存图。

拿树形 DP 经典题 ​​P1352​​ 举例。

分析题目发现一个人有两种情况:

  1. 上司去或自己不想导致不去
  2. 上司没去或自己想导致去

由于有两种情况,所以状态转移方程,就有两个,现在我们用 \(f_{i,0}\) 表示一个人不去其下属活跃值的最大值,\(f{i,1}\) 表示一个人去,其自己活跃值,可得状态转移方程:


\[\begin{cases}& f_{i,0}=\sum \max(f_{x,1},f_{x,0})\\ & f_{i,1}=\sum f_{x,0} + a_i \end{cases} \]


所以代码为:

#include <iostream>

using namespace std;
int n,f[6001][2];
int head[6001],cnt;
int vis[6001],isgen[6001];

struct Node{
int to,next;
}edge[6001];

void add(int t1,int t2) {
edge[++cnt].to=t1;
edge[cnt].next=head[t2];
head[t2]=cnt;
}

void dfs(int root) {
vis[root]=1;
for (int i=head[root];i;i=edge[i].next) {
int x=edge[i].to;
if (!vis[x]) {
dfs(x);
f[root][1]+=f[x][0];
f[root][0]+=max(f[x][0],f[x][1]);
}
}
}

int main() {
cin>>n;
for (int i=1;i<=n;i++) {
cin>>f[i][1];//用 f[i][1] 表示 i 的活跃值
}
for (int i=1;i<n;i++) {
int t1,t2;
cin>>t1>>t2;
add(t1,t2);
isgen[t1]=1;//不是根
}
for (int i=1;i<=n;i++) {
if (!is_gen[i]) {//判断是否为根
dfs(i);
cout<<max(f[i][0],f[i][1]);
return 0;
}
}
}


\(finally\)

树形 DP 的扩展应用还有很多,因为他表示的是一种主次关系,所以还是很实用的。