树形DP

树形DP准确的说是一种DP的思想,将DP建立在树状结构的基础上。整体的思路大致就是用树形的结构存储数据。

要学树形DP之前肯定是要先学会树和图的呀,至少先学会链式前向星,不会的话可以看一下我之前写的博客
链接:【图论】图,实现图(三种方式),二分图 详解

树形DP的关键和实现方法是dfsdfsdfs。

先找到树根,从树根开始运用 dfsdfsdfs 递归,跟dfsdfsdfs一样先初始化,然后递归到叶子节点上为止,把最底层的 f[i][j]f[i][j]f[i][j] 更新完毕,再回来往上走,自底向上地根据题意更新上层的fff数组,最后输出答案即可。

一般基础的题转移方程有两种模式:
选择节点类

{f[i][0]=f[j][1]f[i][1]=max⁡/min⁡(f[j][0],f[j][1])\begin{cases}f[i][0]=f[j][1]\\f[i][1]=\max/\min(f[j][0],f[j][1])\\\end{cases}{f[i][0]=f[j][1]f[i][1]=max/min(f[j][0],f[j][1])​

选择节点式的题首先前提条件是整个数据是由树形结构存储的,或者应该用树形结构存,效率更高什么的,然后会告诉你相邻的节点是不能同时存在的,要求取最大最小值 ,类似P2016 战略游戏、P1352 没有上司的舞会(下面都有详解和题目链接哦)

树形背包类

{f[v][k]=f[u][k]+valf[u][k]=max(f[u][k],f[v][k−1])\begin{cases}f[v][k]=f[u][k]+val\\f[u][k]=max(f[u][k],f[v][k-1])\\\end{cases}{f[v][k]=f[u][k]+valf[u][k]=max(f[u][k],f[v][k−1])​
树形背包,就是背包加上条件,一个物品只有选择了它的主件(根节点)才能选择,类似P2014[CTSC1997]P2014[CTSC1997]P2014[CTSC1997]选课

例题1、P1352 没有上司的舞会

P1352 没有上司的舞会

最基础的入门题,用链式前向星建树,直接用上面总结的转移方程

{f[u][0]+=max(f[v][0],f[v][1]);u不去,那么它的子节点(下属)可去可不去,取最大值即可f[u][1]+=f[v][0];u去了那么它的子节点一定不能去,直接加\begin{cases}f[u][0]+=max(f[v][0],f[v][1]);u不去,那么它的子节点(下属)可去可不去,取最大值即可\\f[u][1]+=f[v][0];u去了那么它的子节点一定不能去,直接加\\\end{cases}{f[u][0]+=max(f[v][0],f[v][1]);u不去,那么它的子节点(下属)可去可不去,取最大值即可f[u][1]+=f[v][0];u去了那么它的子节点一定不能去,直接加​
找到根节点,ans=max(f[rt][0],f[rt][1])ans=max(f[rt][0],f[rt][1])ans=max(f[rt][0],f[rt][1]),非常简单。

#include <bits/stdc++.h>
using namespace std;const int maxn = 6e4 + 7, maxm = 6e4 + 7;
int n, m, root;
int f[maxn][2];
int r[maxn];
int head[maxn], ver[maxm], nex[maxm], tot;
bool vis[maxn];void add(int x, int y)
{ver[tot] = y;nex[tot] = head[x];head[x] = tot ++ ;
}void dfs(int x, int fa)
{f[x][0] = 0;f[x][1] = r[x];for (int i = head[x]; ~i; i = nex[i]) {int y = ver[i];if(y == fa) continue;dfs(y, x);f[x][0] += max(f[y][0], f[y][1]);f[x][1] += f[y][0];}
}int main()
{memset(head, -1, sizeof head);scanf("%d", &n);for (int i = 1; i <= n; ++ i) scanf("%d", &r[i]);for (int i = 1; i <= n - 1; ++ i) {int x, y;scanf("%d%d", &x, &y);add(x, y);add(y, x);vis[x] = 1;}for (int i = 1; i <= n; ++ i)if(vis[i] == 0) {root = i;break;}dfs(root, 0);cout << max(f[root][0], f[root][1]) << '\n';return 0;
}

P2016 战略游戏

这道题的城堡是一颗树
题中有

注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。

所以定义数组f[i][1/0]f[i][1/0]f[i][1/0]表示的是节点i上放士兵或者不放士兵
根据题意,如果当前节点不放置士兵,那么它的子节点必须全部放置士兵,因为要满足士兵可以看到所有的边。
所以有 { } { } { } { } { } { } { } { } { } { } { } { } { } { } { } { } { }f[u][0]+=f[v][1]f[u][0]+=f[v][1]f[u][0]+=f[v][1]{ } { } { } { } { } { } { } { } { } { } { } 其中v是u的子节点
如果当前节点放置士兵,它的子节点选不选已经不重要了(因为树形dp自下而上,上面的节点不需要考虑),所以{ } { } { } { } { } { } { } { } { } { } { } { } { } { } { } f[u][1]+=min(f[v][0],f[v][1])f[u][1]+=min(f[v][0],f[v][1])f[u][1]+=min(f[v][0],f[v][1])

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 2e3 + 7, maxm = 4e3 + 7;int n, m;
int head[maxn], nex[maxm], ver[maxm], tot;
int f[maxn][2];
int din[maxn];
int rt = -1;void add(int x, int y)
{ver[tot] = y;nex[tot] = head[x];head[x] = tot ++ ;
}void init()
{memset(head, -1, sizeof head);tot = 0;
}void dfs(int x, int fa)
{f[x][1] = 1;f[x][0] = 0;for (int i = head[x]; ~i; i = nex[i]) {int y = ver[i];if (y == fa) return ;dfs(y, x);f[x][0] += f[y][1];f[x][1] += min(f[y][0], f[y][1]);}
}int main()
{init();scanf("%d", &n);for (int i = 0; i <= n - 1; ++ i) {int x, k;scanf("%d%d", &x, &k);for (int j = 1; j <= k; ++ j) {int r;scanf("%d", &r);din[r] ++ ;add(i, r);}}for (int i = 0; i <= n - 1; ++ i)if(din[i] == 0)rt = i;dfs(rt, -1);cout << min(f[rt][0], f[rt][1]) << "\n";return 0;
}

双倍经验UVA1292 Strategic game

只有输入有些许的不同

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=4000;
const ll INF=1e9+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;template<typename T>void read(T &x)
{x=0;char ch=getchar();ll f=1;while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}x*=f;
}struct node
{ll v,nex;
}e[N];ll head[N],cnt,n,m,k,x;
ll f[N][2];//二维的只需要0或1即可,开大了memset会超时
ll t,arr[N],rt;inline void add(ll u,ll v)
{e[++cnt].nex=head[u];e[cnt].v=v;head[u]=cnt;
}inline void init()
{memset(f,0,sizeof f);memset(head,0,sizeof head);memset(arr,0,sizeof arr);cnt=0;
}
void dfs(ll u)
{f[u][0]=0,f[u][1]=1;//站或不站,站则至少需要1名士兵for(ll i=head[u];i;i=e[i].nex){dfs(e[i].v);//往下遍历f[u][0]+=f[e[i].v][1];//若不站则相邻的必须站有士兵f[u][1]+=min(f[e[i].v][1],f[e[i].v][0]);}
}int main()
{while(scanf("%lld",&n)!=EOF){init();over(j,1,n){ll a,b;scanf("%lld:(%lld)",&a,&b);over(i,1,b){ll c;scanf("%lld",&c);arr[c]++;add(a,c);//树是有向图}}over(i,0,n){if(!arr[i])//找根{rt=i;break;}}dfs(rt);printf("%lld\n",min(f[rt][1],f[rt][0]));}return 0;
}

题型2、P2014 [CTSC1997]选课

P2014 [CTSC1997]选课

题意为选一门课前要看它是否有前提条件:即选了一门主课才能选 “副科”,所以可以树形背包来做。
注意是不能用分组背包来做,因为这道题附件有很多个,光是两个附件的分组背包就需要四个转移方程,在这里根本没法做。
链式前向星建树。
本身这道题的数据是一组森林,但是森林很难一起dfs所以就把所有的树根都以0为根节点建一颗大树,直接链式前向星前序遍历即可。
本题最多能选M节课
转移方程 f[p][j]f[p][j]f[p][j]是指f[以p为根节点][剩余可选课数]f[以p为根节点][剩余可选课数]f[以p为根节点][剩余可选课数]
因为每门课都只能选一次。所以类似01背包,因此倒序来压缩空间,从三维压缩到二维
转移方程:
f[p][j]=max(f[p][j],f[v][k]+f[p][j−k])f[p][j]=max(f[p][j],f[v][k]+f[p][j-k])f[p][j]=max(f[p][j],f[v][k]+f[p][j−k])
就类似01背包拿当前节点的子节点或者不拿。(子节点是必须父节点被选上的时候才可以选子节点,所以用树形背包做)
解释的应该很清楚了,代码也非常简单,有问题的话就问我

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
using namespace std;
typedef long long ll;
const ll N=1e3+7;
const ll mod=2147483647;
const double EPS=1e-6;
struct node
{ll u,v,pre;
}edge[N];
ll head[N],n,m,f[N][N],cnt;
inline void init()
{memset(head,-1,sizeof head);memset(f,0,sizeof f);cnt=0;
}
inline void add(ll u,ll v)
{edge[++cnt].pre=head[u];edge[cnt].v=v;head[u]=cnt;
}
inline void dfs(ll p)
{for(int i=head[p];~i;i=edge[i].pre){ll v=edge[i].v;dfs(v);for(int j=m+1;j>=1;--j){for(int k=0;k<j;++k){f[p][j]=max(f[p][j],f[v][k]+f[p][j-k]);}}}
}
int main()
{init();scanf("%lld%lld",&n,&m);for(int i=1;i<=n;++i){ll a,b;scanf("%lld%lld",&a,&b);f[i][1]=b;add(a,i);}dfs(0);printf("%lld\n",f[0][m+1]);return 0;
}

注:如果您通过本文,有(qi)用(guai)的知识增加了,请您点个赞再离开,如果不嫌弃的话,点个关注再走吧,日更博主每天在线答疑 ! 当然,也非常欢迎您能在讨论区指出此文的不足处,作者会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧

【树形DP】树形DP入门详解+例题剖析相关推荐

  1. 【数据结构】单调栈和单调队列 详解+例题剖析

    算法:单调栈和单调队列 一.单调栈和单调队列 二.单调栈例题 1.模板题入门 2.不懂不要急,看这道题 三.单调队列例题 1.入门 2.进阶 一.单调栈和单调队列 单调栈和单调队列与普通的栈,队列不同 ...

  2. 【基础算法】二分法(二分答案,二分查找),三分法,Dinkelbach算法,算法详解+例题剖析

    目录 一 . 二分法 二分搜索得要求: 二分查找步骤: 二分答案: 玄学的二分(二分答案) 二 . 三分法 例题 三.01分数规划问题相关算法与题目讲解(二分法与Dinkelbach算法) 一 . 二 ...

  3. 【算法】差分与前缀和 算法详解+例题剖析

    目录 一.前缀和 二. 差分思想 1.静态数组的区间求和问题 2.静态维护区间加等差数列的求和问题 三.二维前缀和 二维前缀和例题P2280 [HNOI2003]激光炸弹 四.例题 例题一:差分+前缀 ...

  4. Jetpack Compose入门详解(实时更新)

    Jetpack Compose入门详解 前排提醒 前言(Compose是什么) 1.实战准备 一.优势与缺点 二.前四课 三.标准布局组件 1.Column 2.Row 3.Box 四.xml和com ...

  5. linux 日志按大小切割_nginx入门详解(六)- 日志切割

    上一章讲解了nginx的目录加密功能,本章重点介绍nginx的日志切割. 笨办法学linux:nginx入门详解(五)- 目录加密​zhuanlan.zhihu.com 在第二章,我们探讨了nginx ...

  6. python怎么安装myqr模块-python二维码操作:对QRCode和MyQR入门详解

    python是所有编程语言中模块最丰富的 生活中常见的二维码功能在使用python第三方库来生成十分容易 三个大矩形是定位图案,用于标记二维码的大小.这三个定位图案有白边,通过这三个矩形就可以标识一个 ...

  7. python语言编程基础-Python语言入门详解!快速学成Python!

    原标题:Python语言入门详解!快速学成Python! 很多技能是被职场所需要的,但很可惜... 这些技能在大学中并学习不到. 大学和职场现实存在的横沟对大部分同学来说难以跨越或碰得头破血流... ...

  8. python语言入门m-Python语言入门详解!快速学成Python!

    今日主题 "Python语言入门详解" 近两年来,Python语言借着数据科学和人工智能的"东风"成为了最流行的编程语言--街头巷尾人们口口相传.同时,Pyth ...

  9. python语言入门详解-python初级教程:入门详解

    python初级教程:入门详解 Crifan Li 目录 前言 .................................................................... ...

最新文章

  1. ECCV2018目标检测(object detection)算法总览
  2. 将Sublime Text 3设置为Python全栈开发环境
  3. SEAM学习(一)-----安装运行example
  4. 寄存器和pin_16x2 LCD的PIN图和寄存器
  5. 实例29:python
  6. STM32H743+CubeMX-定时器TIM输出PWM(PWM Generation模式)+ 中断
  7. 12.2 新特性:RMAN 自动恢复到 REDO 终点的步骤简化
  8. python中str表示什么意思_python的str是什么类型
  9. markdown常用字体
  10. 关于Android中使用Enum的一点总结
  11. 现实迷途 第八章 不轨企图
  12. EPSON爱普生系列打印机清洗更换墨盒方法
  13. 七周成为数据分析师教程
  14. android图片背景颜色透明度,android:设置背景图片、背景颜色透明
  15. Day715. 适配不同的类型的switch匹配 -Java8后最重要新特性
  16. [deepstream][原创]更改deepstream_test1_app在弹出视频上显示fps
  17. 【如何删除taskmer.exe进程灰鸽子木马】
  18. CSRF与钓鱼链接攻击
  19. 【Cxinny】微信小程序笔记
  20. java咖啡机故障5_咖啡机不得不知道的常见故障及解决方法

热门文章

  1. 探索laravel里的encrypt和decrypt实现
  2. 《程序员》与我的程序员之路
  3. 智能指针的拷贝构造函数和移动构造函数的作用和目的
  4. go generate
  5. 怎么让文字变成语音?如何将文字自动生成语音?
  6. tp5模型能实现事务吗_实现工作单元-通过事务模型处理域对象
  7. 罗密欧与朱丽叶的迷宫问题----回溯法
  8. dhu复试基础——76 字符串排序
  9. cc攻击教程,搭建教程
  10. 《武士零》开发者:怎样设计玩家喜欢的过场动画?