声明

https://blog.csdn.net/no1_terminator/article/details/77824790

参考课件和讲授来自Accelerator

找树的直径

树的直径定义为一个树中最长的一条链。

  1. 一种做法比较显然,我们可以大力 DP,维护出一个节点向下的最长链F(x)和次长链G(x),保证F,G不出自同一个儿子,然后用二者加和来更新答案,同时更新父亲节点的最长链和次长链
  2. 另外一种做法,则可以这样:随便选择一个点,然后找到距离这个点最远的点 A, 再以 A 为源点,找到距离 A 最远的一个点 B, AB 路径上的点就是直径。

对于第一种做法比较适合拓展到其他形式的 DP 上去,更新时维护次小的想法也比较适合推荐。同时可以求出任意一个子树的直径

第二种方法我们可以拿出一些有利于解题的性质

第二种方法不支持负权,这需要注意下。

找树的重心

对于一棵 n 个节点的无根树,找到一个点 A,使得把树变成以该点为根的有根树时,最大子树的结点数最小。A 叫做重心。

求法很简单,求 size 即可。

容易发现重心的各个儿子的 $ size<= n/2 $

支配集与独立集

1.求一个最大点集使得其中点的个数尽可能多且该集合中不存在两个点有边相连

2.求一个最小点集使得树上的每个点都存在一个集合中的点与其相连

两个问题很有代表性,这里讲一下解法。

第一个问题比较好解决,考虑令 f(x) 表示 x 点在集合中以x 为根的子树能选取的最多点数,g(x) 表示x 点不在集合中,以 x 为根的子树能选取的最多点数。

考虑按照题意的合法性转移即可。

f(x) = ∑g(son)

g(x) =∑ max f(son), g(son)

第二个问题相对复杂,我们称选择的点能够“覆盖”与其相连的点,那么考虑一个点的合法状态有 3 种,分别设选择该点的状态为 f(x),这个点被儿子覆盖g(x), 这个点被父亲覆盖h(x)f, h 函数的转移都很简单对于 g, 分类讨论即可。

DP 的两种处理方法

前面默认我们都是使用了 DFS 来递归处理子树,然后回溯更新节点,但是实际操作中会存在问题。

Windows 下默认栈空间大小为 4Mb, Linux 下为 8Mb, 大量递归会堆栈溢出

考虑这个转移的过程只需要所有的儿子都被更新完。我们BFS 这颗树,然后按照bfs序倒过来处理 DP 是可以得到同样的效果的。

至此我们解决了这个问题。

树形 DP 为什么相较其他 DP 来比有难度

  1. 在树上进行,相较于序列,更新的方式更多,对思维难度和代码实现难度要求都更高。
  2. 对 DP 优化的考察更为明显,如何通过更优秀的状态表示将一个复杂度更高的动态规划降维
  3. 背包问题的拓展以及树上背包

    接下来我们将通过习题来解决这些问题。

POJ 1463

给定一棵树,被选定的点可以覆盖所有和他向连的边,求覆盖所有的边需要最少多少个点? $n <=10^5 $。

下放到点即可

#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int MAX = 1500+9;
const int MAXM = 1000000; int n;
int f[MAX], g[MAX];
//f[x]表示选定x,保证子树合法,子树x内最少需要选定的节点数
//g[x]表示不选x,.... struct edge{
int y, next;
}e[MAXM];
int head[MAX], cnt;
void add_edge(int x, int y) {
e[++cnt].y = y;
e[cnt].next = head[x];
head[x] = cnt;
} void dfs(int x) {
g[x] = 0;
f[x] = 1;//初始化
for(int i = head[x]; i; i = e[i].next) {
dfs(e[i].y);
g[x] += f[e[i].y];
f[x] += min(f[e[i].y], g[e[i].y]);
}
} int main() {
int k, x, y, root;
while(~scanf("%d",&n)) {
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(head, 0, sizeof(head));
for(int i = 1; i <= n; i++) {
scanf("%d:(%d)",&x, &k);
if(i == 1) root = x;
for(int j = 1; j <= k; j++) {
scanf("%d",&y);
add_edge(x, y);
}
}
dfs(root);
printf("%d\n",min(f[root], g[root])) ;
}
}

POJ 1155

一个树形网络,编号为 1 的是广播站,叶子节点为广播接收者,费用是从根节点到叶子结点的边权和,价值是所有选中的叶子结点价值和。

问在保证广播站收益不亏本的情况下最多能选择多少叶子结点?

数据范围支持 \(n^3\)

f[i] [j] : i节点下端有j个节点的收益 时的最大价值和

先粘上老师的代码,自己不会...

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define N 3000+5
#define M 6000+5
const int inf = -100000000;
using namespace std; int head[N];
int num[N]; struct graph
{
int next,to,val;
graph () {}
graph (int _next,int _to,int _val)
:next(_next),to(_to),val(_val){}
}edge[M]; int cnt; inline void add(int x,int y,int z)
{
edge[++cnt] = graph(head[x],y,z);
head[x] = cnt;
} inline int read()
{
int x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
} int f[N][N]; void init()
{
memset(head,0,sizeof head);
memset(num,0,sizeof num);
cnt = 0;
} void DFS(int x)
{
for(int i=head[x];i;i=edge[i].next)
{
DFS(edge[i].to);
// for(int j=num[x];j>=0;--j)
// for(int k=1;k<=num[edge[i].to];++k)
// f[x][j+k] = max(f[x][j+k],f[x][j]+f[edge[i].to][k]-edge[i].val);
// num[x] += num[edge[i].to];
num[x] += num[edge[i].to];
for(int j=num[x];j>=0;--j)//一定要是j>="0",应该是因为它可以都不选,它代价-价值为负
for(int k=1;k<=num[edge[i].to];++k)
f[x][j] = max(f[x][j],f[x][j-k]+f[edge[i].to][k]-edge[i].val);
}
} int main()
{
int n , m;
//freopen("13.in","r",stdin);
while(cin >> n >> m)
{
init();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
f[i][j] = inf;
for(int i=1;i<=n-m;++i)
{
int k = read();
num[i] = 0;
for(int j=1;j<=k;++j)
{
int y = read() , z = read();
add(i,y,z);
}
}
for(int i=n-m+1;i<=n;++i)
num[i] = 1, f[i][1] = read();//num[]一定要在这初始化,不知道为啥..求解!
DFS(1);
for(int i=m;i>=0;--i)if(f[1][i]>=0){printf("%d\n",i);break;}
}
}

poj 1947(luogu P1272

树形分组背包(我并不清楚....

注意初始化与定义相匹配

注意两个代码实现的细节

参考博客


#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 150+9;
const int INF = 2147000047;
inline int read() {
char ch = getchar(); int f = 1, x = 0;
while(ch<'0' || ch>'9') {if(ch == '-') f = -1; ch = getchar();}
while(ch>='0' && ch<='9') {x = (x<<3)+(x<<1)+ch-'0'; ch = getchar();}
return x*f;
} int n, p, ans;
int head[N], cnt;
struct edge{
int y, nxt;
}e[N<<1];
void add_edge(int x, int y) {
e[++cnt].y = y;
e[cnt].nxt = head[x];
head[x] = cnt;
} int f[N][N], size[N];
//f[i][j]:保留i,使树i只含有j个节点的最小剪枝数
//不选i的情况另外考虑 void dfs(int x) {
size[x] = 1;
int tmp;
for(int i = head[x]; i; i = e[i].nxt) {
dfs(e[i].y);
for(int j = size[x]; j >= 1; --j) {
for(int k = 1; k <= size[e[i].y]; ++k) //考虑选择子树
f[x][j+k] = min(f[x][j+k], f[x][j]+f[e[i].y][k]-1);
//最开始初始化f时,将每个节点与它的直系儿子断开,使得x->e[i].y这条边被删去,现在选择了e[i].y,需要补上
}
size[x] += size[e[i].y];
}
} int tmp_son[N], is[N], root;
int main() {
n = read(), p = read();
int x, y;
for(int i = 1; i < n; ++i) {
x = read(), y = read();
is[y] = 1, tmp_son[x]++;//统计每个节点的直系儿子数
add_edge(x, y);
}
memset(f, 0x3f3f3f3f, sizeof(f));
for(int i = 1; i <= n; ++i) {
if(!is[i]) root = i;//找到根(题目只有一颗树,那就是有根的
f[i][1] = tmp_son[i];//顺带初始化:分开i与直系儿子即可
}
dfs(root);
int ans = f[root][p];
for(int i = 1; i <= n; i++) //按题目要求,子树里也行
if(f[i][p] < ans) ans = f[i][p]+1;//除根节点外,其它点需要切除与父亲的联系
printf("%d\n", ans);
return 0;
}

poj 1935

给你一个树,一些点和一个根,从根出发遍历给定的点(任意顺序),最后不必回根的最小路径. n <= 2*10^5

过给定点的距离和

分析来自150137

如果你精通数据结构的话,这题会有一些高端做法,支持数据范围扩大若干倍。这里只考虑这个问题。

首先我们一定是停在一个距离根最远的点,答案就是往返-最远点到跟的距离。求每个点到跟的距离很简单,考虑如何求出过给定点的距离和。

做法也很简单,如果一个点被标记,那就沿着这棵树向上爬,爬到最后一个没被标记的点,并更新答案,将沿途标记。

每个点最多被标记一次,所以复杂度 O(n).

总结

通过上面的题,我们发现,这些题还是比较简单的,状态表示常常与他给的限制和一些必要的元素组成,往往还会与背包相联系。

然而这些都是之前非常熟练的东西,我们只需要简单的处理一些小的细节。

——150137

树型DP(2)的更多相关文章

  1. POJ3659 Cell Phone Network(树上最小支配集:树型DP)

    题目求一棵树的最小支配数. 支配集,即把图的点分成两个集合,所有非支配集内的点都和支配集内的某一点相邻. 听说即使是二分图,最小支配集的求解也是还没多项式算法的.而树上求最小支配集树型DP就OK了. ...

  2. POJ 3342 - Party at Hali-Bula 树型DP+最优解唯一性判断

    好久没写树型dp了...以前都是先找到叶子节点.用队列维护来做的...这次学着vector动态数组+DFS回朔的方法..感觉思路更加的清晰... 关于题目的第一问...能邀请到的最多人数..so ea ...

  3. 【XSY1905】【XSY2761】新访问计划 二分 树型DP

    题目描述 给你一棵树,你要从\(1\)号点出发,经过这棵树的每条边至少一次,最后回到\(1\)号点,经过一条边要花费\(w_i\)的时间. 你还可以乘车,从一个点取另一个点,需要花费\(c\)的时间. ...

  4. 洛谷P3354 Riv河流 [IOI2005] 树型dp

    正解:树型dp 解题报告: 传送门! 简要题意:有棵树,每个节点有个权值w,要求选k个节点,最大化∑dis*w,其中如果某个节点到根的路径上选了别的节点,dis指的是到达那个节点的距离 首先这个一看就 ...

  5. 【POJ 3140】 Contestants Division(树型dp)

    id=3140">[POJ 3140] Contestants Division(树型dp) Time Limit: 2000MS   Memory Limit: 65536K Tot ...

  6. Codeforces 581F Zublicanes and Mumocrates(树型DP)

    题目链接  Round 322 Problem F 题意  给定一棵树,保证叶子结点个数为$2$(也就是度数为$1$的结点),现在要把所有的点染色(黑或白) 要求一半叶子结点的颜色为白,一半叶子结点的 ...

  7. ZOJ 3949 (17th 浙大校赛 B题,树型DP)

    题目链接  The 17th Zhejiang University Programming Contest Problem B 题意  给定一棵树,现在要加一条连接$1$(根结点)和$x$的边,求加 ...

  8. BZOJ 1564 :[NOI2009]二叉查找树(树型DP)

    二叉查找树 [题目描述] 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结 ...

  9. Codeforces 149D Coloring Brackets(树型DP)

    题目链接 Coloring Brackets 考虑树型DP.(我参考了Q巨的代码还是略不理解……) 首先在序列的最外面加一对括号.预处理出DFS树. 每个点有9中状态.假设0位不涂色,1为涂红色,2为 ...

  10. HDU 5905 Black White Tree(树型DP)

    题目链接  Black White Tree 树型DP,设$f[i][j]$为以$i$为根的子树中大小为$j$的连通块中可以包含的最小黑点数目. $g[i][j]$为以$i$为根的子树中大小为$j$的 ...

随机推荐

  1. MarkDown使用小技巧

    写作目的 笔者看完一些MarkDown入门文字后,发现很多文档对一些容易遇到的细微问题缺少解释.本文意欲稍作补充. 目标读者 使用MarkDown仅为了排版出更好看的文章,而不关注技术细节,不懂HTM ...

  2. Windows 10 任务栏添加网易云音乐控制按钮

      软件背景: 算是老帖新发,之前有朋友分享过一个很好用的工具,但可能是因为网易云软件更新后,导致控件失灵了,只剩下歌词控件有用了,所以今天用python重新写了一个小工具,发出来分享给大家,附上之前 ...

  3. MYSQL5.7的安装(yum、二进制、编译安装)

    目录 一.环境说明 二.YUM安装 1.安装MYSQL-YUM源 2.安装说明 3.安装前的准备 4.安装 5.启动 三.变更root密码 四.BINARY-INSTALL 1.基础环境准备 2.建立 ...

  4. 验证登录的前世今生:session、cookie

    参考地址:彻底理解cookie,session,token 使用JSON Web Token设计单点登录系统 1.很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录 ...

  5. 使用 Hbuilder 连接手机调试移动端项目

    点击界面上的浏览器右侧的倒三角.   弹出列表以后,点击最后一行 “设置web服务器...”.继续弹出,点击右下角的“外置Web服务器设置”.   点新建.   弹出框后,填入“名称”和“浏览器运行U ...

  6. [题解向] PAM简单习题

    \(1\) LG5496 [模板]回文自动机 对于 \(s\) 的每个位置,请求出以该位置结尾的回文子串个数. \(|s|\leq 1e6\) 然后就是PAM的板子题咋感觉好像没有不是很板的PAM题呢 ...

  7. 推荐书单(网课)-人生/编程/Python/机器学习-130本

    目录 总计(130本) 一.在读 二.将读 三.已读 非专业书单(77本) 四.已读 专业书单(53本) 五.已看网课(8个) 六.在看网课 一个人如果抱着义务的意识去读书,便不了解读书的艺术.--林 ...

  8. Batchnorm原理详解

    Batchnorm原理详解 前言:Batchnorm是深度网络中经常用到的加速神经网络训练,加速收敛速度及稳定性的算法,可以说是目前深度网络必不可少的一部分. 本文旨在用通俗易懂的语言,对深度学习的常 ...

  9. PHP面试常考之设计模式——建造者模式

    建造者模式 介绍 建造者模式又名生成器模式,是一种对象构建模式.它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象. 建造者模式是一步一步创建 ...

  10. C++入门到理解阶段二基础篇(1)——简介与环境安装

    1.C++ 简介 C++ 是一种静态类型的.编译式的.通用的.大小写敏感的.不规则的编程语言,支持过程化编程.面向对象编程和泛型编程. C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点. ...