树形DP
树形DP没有固定的模板,见多了题,就知道该如何分析,如何设计状态了。所以该篇博客以例题来进行讲解。
树的直径
本题是一道树的直径板子题。
求树的直径有两种方法:
(1)两遍 dfs。(2)树形DP。
时间复杂度都是\(O(N)\),但两遍dfs需要满足树中不存在负边。
这里我们用树形DP来解决该题。
思路
首先,选择任意一点为根。
如果一个点是直径上的一点,那树的直径等于从该点出发的最长链+从该点出发的次长链。
设计状态
设 \(dp[i][0/1]\) 分别表示以 \(i\) 点为根的子树中从 \(i\) 出发的最长链和次长链长度。
状态转移
树形DP一般就是递归由子孙节点的信息去更新祖先节点的信息。
设\(son_j\) 表示 \(i\) 节点的儿子。
因为最长链和次长链不可以重合,所以我们用最长链去更新次长链。
①若 \(dp[i][0]<dp[son_j][0]+1\),则\(dp[i][1]=dp[i][0]\),\(dp[i][0]=dp[son_j][0]+1\)。
②否则,若\(dp[i][1]<dp[son_j][0]+1\),则 \(dp[i][1]=dp[son_j][0]+1\)。
用代码解释就是:
点击查看代码
if(dp[x][0]<dp[v][0]+1){
dp[x][1]=dp[x][0];
dp[x][0]=dp[v][0]+1;
}else if(dp[x][1]<dp[v][0]+1){
dp[x][1]=dp[v][0]+1;
}
初始化
都为0。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-1') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n;
int dp[10005][2],ans;
vector<int> g[10005];
void dfs(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(v==fa) continue;
dfs(v,x);
if(dp[x][0]<dp[v][0]+1){
dp[x][1]=dp[x][0];
dp[x][0]=dp[v][0]+1;
}else if(dp[x][1]<dp[v][0]+1){
dp[x][1]=dp[v][0]+1;
}
}
ans=max(ans,dp[x][0]+dp[x][1]);
}
int main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
cout<<ans;
return 0;
}
树的中心
这个蒟蒻没有权限看这道题
题意:一棵树,边有权,找出一个点,使得这个点距离其他点的最大距离最小。
思路
首先,选择任意一点为根。
一个点的最远点的位置有两种情况。
(1)以该点为根的子树内;
(2)以该点为根的子树外。
子树内的好求,和上面那题一模一样。
字数外呢?新设一个变量。
设计状态
设 \(dp[i][0/1]\) 分别表示以 \(i\) 点为根的子树中从 \(i\) 出发的最长链和次长链长度。
设 \(c[i][0/1]\) 分别表示 \(dp[i][0/1]\) 从哪个点转移过来。
设 \(u[i]\) 表示以 \(i\) 为根的子树外的距离 \(i\) 最远的点。
状态转移
首先一遍树形DP求出 \(dp[i][0/1]\) 以及 \(c[i][0/1]\)。
同上题。
然后,设 \(pre[i]=x\) 为 \(i\) 的父亲。
①若 \(c[x][0]!=i\),则 \(u[i]=\max(u[x],dp[x][0])+w\);
②否则,若 \(c[x][1]!=i\),则 \(u[i]=\max(u[x],dp[x][1]])+w\)。
初始化
都为0。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n;
int dp[10005][2],c[10005][2],out[10005],ans=0x7fffffff;
vector<pair<int,int> > g[10005];
void dfs1(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i].second;
int w=g[x][i].first;
if(v==fa) continue;
dfs1(v,x);
if(dp[x][0]<dp[v][0]+w){
c[x][1]=c[x][0];
dp[x][1]=dp[x][0];
c[x][0]=v;
dp[x][0]=dp[v][0]+w;
}else if(dp[x][1]<dp[v][0]+w){
c[x][1]=v;
dp[x][1]=dp[v][0]+w;
}
}
}
void dfs2(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i].second;
int w=g[x][i].first;
if(v==fa) continue;
if(c[x][0]!=v) out[v]=max(out[x],dp[x][0])+w;
else if(c[x][1]!=v) out[v]=max(out[x],dp[x][1])+w;
dfs2(v,x);
}
}
int main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
g[u].push_back(make_pair(w,v));
g[v].push_back(make_pair(w,u));
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++) ans=min(ans,max(out[i],dp[i][0]));
cout<<ans;
return 0;
}
没有上司的舞会
思路
先找到根,简化一下题意就是父亲和儿子不能同时被选,每个点只有两种状态,选或不选,且仅影响自己的父亲与儿子。树形DP。其实这里解释的很草率,但这就是个树形DP。可能做多了就有这种感觉了吧
设计状态
设 \(dp[i][0/1]\) 表示分别表示该点不选和选,对于以 \(i\) 为根的子树的最大权值。
状态转移
设 \(son_j\) 为 \(i\) 点的儿子。
\(i\) 不选,那么儿子可选可不选:\(dp[i][0]+=\max(dp[son_j][0],dp[son_j][1])\)。
\(i\) 选,那么儿子绝对不能选:\(dp[i][1]+=dp[son_j][0]\)。
最后答案就是 \(\max(dp[root][0],dp[root][1])\)。
初始化
\(dp[i][0]=0\),\(dp[i][1]=w[i]\)。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n,ha[6005];
bool ind[6005];
vector<int> g[6005];
int f[6005][2];
int roott;
void inp(){
n=read();
for(int i=1;i<=n;i++){
ha[i]=read();
}
//每个人的快乐值
for(int i=1;i<n;i++){
int upp,loww;
loww=read(),upp=read();
g[upp].push_back(loww);
ind[loww]=1;
}
//建树,ind为入度,用来找根(最大的上司)
}
void findroott(){
for(int i=1;i<=n;i++){
if(!ind[i]){
roott=i;
break;
}
}
}
//找根
void recursion(int x){
f[x][0]=0;
f[x][1]=ha[x];
int k=g[x].size();
for(int i=0;i<k;i++){
int v=g[x][i];
recursion(v);
f[x][0]+=max(f[v][0],f[v][1]);//自己不去,和自己相连的人可以去也可以不去,取最大值
f[x][1]+=f[v][0];//自己去,自己的儿子就不能去
}
}
//递归求值
int main(){
inp();
findroott();
recursion(roott);
printf("%d",max(f[roott][0],f[roott][1]));
//输出根去的价值大,还是不去的价值大
return 0;
}
保安站岗
思路
以点被谁覆盖进行分类:(1)被自己覆盖;(2)被父亲覆盖;(3)被儿子覆盖。
设计状态
- \(dp[i][0]\)表示以\(i\)为根的整棵子树被覆盖,\(i\)被自己覆盖的最小代价
- \(dp[i][1]\)表示以\(i\)为根的整棵子树被覆盖,\(i\)被父亲覆盖的最小代价
- \(dp[i][2]\)表示以\(i\)为根的整棵子树被覆盖,\(i\)被儿子覆盖的最小代价
状态转移方程
- \(dp[i][0]=\sum\limits_{son[i]}\min \{dp[son[i]][0],dp[son[i]][1],dp[son[i]][2]\}+val[i]\)
这个比较好想,自己控制了自己,儿子可以被爹,自己,儿子控制,三种情况取最小。最后再加上控制自己的代价。
2. \(dp[i][1]=\sum\limits_{son[i]}\min\{dp[son[i]][0],dp[son[i]][2]\}\)
自己被父亲控制,所以儿子要么被自己控制,要么被儿子控制。这里不需要加上自己被父亲控制的代价,我们看看哪里用到了\(dp[i][1]\),是不是只有转移\(dp[i][0]\)时,但\(dp[i][0]\)已经加上了\(val[i]\)如果这里也加上自己被父亲控制的代价,就重复计算了。
3. \(dp[i][2]=\sum\limits_{son[i]}\min\{dp[son[i]][0],dp[son[i]][2]\}+minn\)
这里就需要注意了,自己被儿子控制(只要有一个儿子控制自己就行),儿子可能自己控制自己,也可能被自己的儿子控制。两者取最小。这里又分出两种情况
- 如果有任意一个儿子,是自己控制自己的,也就满足了该点可以被儿子控制。不用加minn
- 如果所有的儿子都是被自己的儿子控制,就无法满足该点被儿子控制,所以至少要找出一个儿子来自己控制自己,为了使答案更小,所以就找\(minn=dp[son[i]][0]-dp[son[i]][2]\)最小的那个儿子,加上这个值,就满足了该点被其中一个儿子控制了。
最后答案:
根节点没有父亲,所以答案就取\(\min\{dp[1][0],dp[1][2]\}\)。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n,w[1505],dp[1505][3];
vector<int> g[1505];
void dfs(int x,int fa){
dp[x][0]=w[x];
bool mark=0;
int minn=0x7fffffff;
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(v==fa) continue;
dfs(v,x);
dp[x][0]+=min(dp[v][1],min(dp[v][2],dp[v][0]));
dp[x][1]+=min(dp[v][0],dp[v][2]);
if(dp[v][2]<dp[v][0]){
dp[x][2]+=dp[v][2];
minn=min(dp[v][0]-dp[v][2],minn);
}else{
dp[x][2]+=dp[v][0];
mark=1;
}
}
if(mark==0) dp[x][2]+=minn;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int u=read();
w[u]=read();
int x=read();
while(x>0){
x--;
int v=read();
g[u].push_back(v);
g[v].push_back(u);
}
}
dfs(1,0);
printf("%d",min(dp[1][0],dp[1][2]));
return 0;
}

浙公网安备 33010602011771号