声明

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. ping测试丢包率

    测试环境:Centos 6.4 增加参数:-i 例如: #ping -i 0.01 172.16.3.1 则每隔0.01秒ping一次

  2. [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析【转】

    转自:https://www.cnblogs.com/x_wukong/p/8573602.html 转自;https://www.cnblogs.com/aaronLinux/p/5890924.h ...

  3. Winclone 8 Mac增强汉化版 Windows分区备份迁移工具 8.0.1

    winclone 8 for Mac版是一款系统清理工具winclone的Mac平台版本,winclone Mac版可以将Bootcamp分区安装的windows进行克隆也可将克隆文件传回Bootca ...

  4. JSON格式日期的转换

    扒来的链接: https://blog.csdn.net/zhang33565417/article/details/99676975 感谢这位哥们儿的分享!

  5. 一、itk在VS2019上面的安装 和例子(HelloWorld)运行

    一.Itk简介 vtk是专门用于医疗图像处理的函数库,类似opencv. 这篇博客主要是讲解安装vtk之后的例子的运行,即如何构建自己的第一个ITK例子 二.Itk安装 Itk安装参考这篇博客: ht ...

  6. poj 3253 Fence Repair 贪心 最小堆 题解《挑战程序设计竞赛》

    地址 http://poj.org/problem?id=3253 题解 本题是<挑战程序设计>一书的例题 根据树中描述 所有切割的代价 可以形成一颗二叉树 而最后的代价总和是与子节点和深 ...

  7. dom0、dom2、dom3事件

    https://www.jianshu.com/p/3acdf5f71d5b addEventListener():可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用. removeEven ...

  8. 基础知识 Asp.Net MVC EF各版本区别

    原文:https://www.cnblogs.com/liangxiaofeng/p/5840754.html 2009年發行ASP.NET MVC 1.0版 2010年發行ASP.NET MVC 2 ...

  9. SSM框架(Spring + Spring MVC + Mybatis)搭建

    Spring是一个轻量级的框架,用到了注解和自动装配,就是IOC和AOP: SpringMVC是Spring实现的一个Web层,相当于Struts的框架: Mybatis是 一个持久层的框架,在使用上 ...

  10. iOS: 创建静态库,实现自己的API私有使用

    一.介绍 在开发中经常使用到第三方的静态框架,格式基本上就是.framework和.a格式的.使用时,会发现我们只能使用无法修改,这就是静态框架的一个好处,私有性.内部实现的代码只有公开者本人知晓,对 ...