0x54. 动态规划 - 树形DP(习题详解 × 12)

Posted 繁凡さん

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0x54. 动态规划 - 树形DP(习题详解 × 12)相关的知识,希望对你有一定的参考价值。

本系列博客是《算法竞赛进阶指南》的学习笔记,包含书中的部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络 ,由我个人整理总结。部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
%
学习笔记目录链接: 学习笔记目录链接
%
整理的算法模板合集: ACM模板
%
点我看算法全家桶系列!!!


0x54.1 树形DP

给定一棵有 n n n 个结点 n − 1 n-1 n1 条边的普通的树,我们可以任选一个节点作为根节点,将无根树变为有根树,从而定义出每个节点的深度和每棵子树的根。一般是以结点编号(即子树从小到大)的顺序作为 DP 的阶段,或者求出树的拓扑序直接递推。DP 的状态表示,一般第一维是结点的编号,代表以该结点为根的子树,采用递归的形式进行转移,对于每个结点 x x x,先递归在它的每个子结点上进行 DP ,在回溯的时候,从子结点向结点 x x x 进行状态转移。

Problem A. 没有上司的舞会

Luogu P1352

某大学有 n n n 个职员,编号为 1 … n 1\\ldots n 1n

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。

现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 r i r_i ri ,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。

所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

Solution

由于每名职员参加与否只跟他的直接上司即直接父结点是否参加有关,因此我们在转移的时候显然要保存这两个信息,我们只需要分别记录根节点参加时整颗子树的最大快乐值以及根节点不参加时整颗子树的最大快乐值即可。

f [ x , 0 ] f[x, 0] f[x,0] 表示根节点 x x x 不参加,并邀请以 x x x 为根的子树结点参加的最大快乐值。
有转移方程:

f [ x , 0 ] = ∑ y ∈ s o n [ x ] max ⁡ ( f [ y , 0 ] , f [ y , 1 ] ) f[x, 0] = \\sum_{y\\in son[x]}\\max(f[y, 0], f[y, 1]) f[x,0]=yson[x]max(f[y,0],f[y,1])

f [ x , 1 ] f[x, 1] f[x,1] 表示根节点 x x x 参加,并邀请以 x x x 为根的子树结点参加的最大快乐值。

f [ x , 0 ] = r [ x ] + ∑ y ∈ s o n [ x ] f [ y , 0 ] f[x, 0] = r[x] + \\sum_{y\\in son[x]}f[y, 0] f[x,0]=r[x]+yson[x]f[y,0]

由于输入的是有根树,所以我们找到无上司的结点作为树根,直接dfs转移即可。时间复杂度 O ( n ) O(n) O(n)

Code

#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;
}

Problem B. 战略游戏

Luogu P2016

Bob 要建立一个古城堡,城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。

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

请你编一程序,给定一棵树,帮 Bob 计算出他需要放置最少的士兵。

Solution

根据题意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。因此我们需要知道每个士兵是否被放置,所以我们设状态 f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1] 表示结点 x x x 不放/放士兵,以 x x x 为根的子树的最少放置人数。

显然若节点 x x x 不放置士兵, x x x 的子节点必须全部放置满士兵,因此有转移方程:

f [ x ] [ 0 ] = ∑ y ∈ s o n [ x ] f [ y ] [ 1 ] f[x][0]= \\sum_{y\\in son[x]} f[y][1] f[x][0]=yson[x]f[y][1]

若节点 x x x 放置士兵, x x x 的子节点放或者不放均可,取最小值即可。

f [ x ] [ 1 ] = min ⁡ ( f [ y ] [ 0 ] , f [ y ] [ 1 ] ) + 1 f[x][1]=\\min(f[y][0],f[y][1]) + 1 f[x][1]=min(f[y][0],f[y][1])+1

初始化: f [ x ] [ 0 ] = 0 , f [ x ] [ 1 ] = 1 f[x][0] = 0, f[x][1] = 1 f[x][0]=0,f[x][1]=1

直接 d f s dfs dfs 即可,复杂度 O ( n ) O(n) O(n)

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 7, M = 3e6 + 7;;

int n, m, root;
int head[N], ver[M], nex[M], tot;
bool vis[N]; 
int f[N][2];

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] = 1;
	for (int i = head[x]; ~i; i = nex[i]) {
		int y = ver[i];
		if (y == fa) continue;
		dfs(y, x);
		f[x][0] += f[y][1];
		f[x][1] += min(f[y][1], f[y][0]);
	}
}

int main()
{
	memset(head, -1, sizeof head);
	scanf("%d", &n);
	for (int i = 1; i <= n; ++ i) {
		int x, num;
		scanf("%d%d", &x, &num);
		for (int i = 1; i <= num; ++ i) {
			int y;
			scanf("%d", &y);
			add(x, y);
			add(y, x);
			vis[y] = 1;
		}
	}
	for (int i = 0;i <= n - 1; ++ i)
		if(vis[i] == 0) {
			root = i;
			break;
		}
	dfs(root, -1);
	cout << min(f[root][0], f[root][1]) << '\\n';
	return 0;
}

0x54.2 树上背包

Problem A. 选课

Luogu P2014 [CTSC1997]

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N N N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a a a 是课程 b b b 的先修课即只有学完了课程 a a a,才能学习课程 b b b)。一个学生要从这些课程里选择 M M M 门课程学习,问他能获得的最大学分是多少?

输入描述

第一行有两个整数 N N N, M M M 用空格隔开。( 1 ≤ N ≤ 300 1 \\leq N \\leq 300 以上是关于0x54. 动态规划 - 树形DP(习题详解 × 12)的主要内容,如果未能解决你的问题,请参考以下文章

0x53. 动态规划 - 区间DP(习题详解 × 8)

0x56. 动态规划 - 状态压缩DP(习题详解 × 7)

0x52. 动态规划 - 背包(习题详解 × 19)

动态规划区间计数数位统计状态压缩树形DP与记忆化搜索 题解与模板

0x54 树形DP

0x55. 动态规划 - 环形与后效性处理(例题详解 × 6)