首页 > 其他 > 详细

树形 $dp$ $\&$ 状压 $dp$

时间:2021-05-04 23:48:56      阅读:60      评论:0      收藏:0      [点我收藏+]

树形 \(dp\) & 状压 \(dp\)

树形 \(dp\)

题目

给定一棵树 每个节点都为黑色 将某些节点变为白色 使每条边的两端至少有一个白色 问最少需改变的点

\(f_{u, 0} = \sum f_{v, 1}\)

\(f_{u, 1} = \sum \max \{f_{v, 0}, f_{v, 1}\} + 1\)

树上背包

建树

\(f_{u, j} = \max\{f_{v, k} + f_{u, j - k}\}\)

实际上是背包处理所有儿子 再将这些儿子合并

树上背包的优化

(资料来自网络)

翻到了四种 摘录了两种

背包属于泛化物品 合并两个泛化物品的时间复杂度是 \(O(n^2)\) 的 在树上背包中 就是一个点的每个子树的关系 对于子树的合并 有了树上背包的转移方程

for(int i = head[u]; i; i = e[i].nxt)
{
    int v = e[i].v; if(v == pre) continue;
    dfs(v, u);
    for(int j = m; j >= c[u]; j--)
        for(int k = 0; k <= j - c[u]; k++)
            f[u][j] = Max(f[u][j], f[u][j - k] + f[v][k]);
}

优化:

  1. 改变状态

\(f_{u_s, j}\) 表示以 \(u\) 为根 第 \(s\) 个儿子的所有子节点和前 \(s - 1\) 个儿子构成的背包占用 \(j\) 的容量时最大价值

由于深搜对于树的遍历顺序 一个点的儿子一定先于这个点被处理

在处理每个节点 \(v\) 时 将 \(f_u\) 赋值给 \(f_v\) 此时的 \(f_v\) 表示的就是 \(v\) 之前的所有 \(u\) 的子节点加上原本意义上的 \(f_v\) 其实就是上面的状态

转移 \(f_{u, j} = \max\{f_{v, j - c_v} + val_v \}\)

相当于 \(01\) 背包的转移

for(int i = head[u]; i; i = e[i].nxt)
{
    int v = e[i].v; if(v == pre) continue;
    for(int j = 0; j <= m; j++) f[v][j] = f[u][j];
    dfs(v, u);
	for(int j = c[v]; j <= m; j++) f[u][j] = Max(f[u][j], f[v][j - c[v]] + val[v]);
}
  1. 上下界优化

当在以 \(u\) 为根的子树上选择时 选择的物品最大容量不会超过 \(size_u\) 枚举以子节点 \(v\) 为根的子树全部能选的容量时 在子树中选择的物品最大容量不会超过 \(size_v\)

严格的证明并不会

复杂度均摊 \(O(nm)\)

for(int i = head[u]; i; i = e[i].nxt)
{
    int v = e[i].v; if(v == pre) continue;
    dfs(v, u);
    for(int j = Min(siz[u], m); j; --j)
        for(int k = Min(siz[v], m - j + 1); k; --k)
            f[u][j + k] = Max(f[u][j + k], f[u][j] + f[v][k]);
    siz[u] += siz[v];
}

题目

给定一棵 \(n\) 个点的树 每个点有一个权值 一条边两端至少选一个点 问最大收益

同上面的题目

题目

至多选取 \(k\) 个点

所以多了一个容量 变成了一个背包 多维护一维容量

\(f_{i, j, 0/1}\) 表示 到 \(i\) 点 选择 \(j\) 个点时 是否选该点

\(f_{u, j, 1} = \max \{\max\{f_{v, k, 1}, f_{v, k, 0}\} + f_{u, j - k, 1}\} + v_u\)

\(f_{u, j, 0} = \max \{f_{v, k, 1} + f_{u, j - k, 0}\}\)

强制 \(u\) 点选

(数组中小的一维放到外面可以带来常数上的优化)

优化:

记一个子树大小

浪费时间的背包合并

for(int i = siz[u]; i >= 0; i--)
    for(int j = siz[v]; j >= 0; j--)
    {
        f[u][i + j][0] = Max(f[u][i + j][0], f[u][i][0] + f[v][j][1]);
        f[u][i + j][1] = Max(f[u][i + j][1], f[u][i][1] + Max(f[v][j][0], f[v][j][1]));
    }
siz[u] += siz[v];

复杂度从 \(O(n^3)\) 降到 \(O(n^2)\)

树形背包的上下界优化

树的直径

两遍 \(dfs\)

结论 距离一个点最远的点为树的直径上的一个点

树形 \(dp\)

树的最大独立集

\(f_{u, 0} = \sum \max\{f_{v, 0}, f_{v, 1} \}\)

\(f_{u, 1} = \sum f_{v, 0} + 1\)

\(STA\) \(-\) \(Sration\)

给出一颗 \(n\) 个点的树 找一个点满足该点为根时 所有点的深度之和最大

换根 \(dp\)

/*
  Time: 5.4
  Worker: Blank_space
  Source: P3478 [POI2008]STA-Station
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define ll long long
/*--------------------------------------头文件*/
const int C = 1e6 + 7;
/*------------------------------------常量定义*/
int n, id;
ll f[C], siz[C], sum;
struct edge {int v, nxt;} e[C << 1];
int head[C], ecnt;
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
void dfs1(int u, int pre) {
	siz[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].v; if(v == pre) continue;
		dfs1(v, u); siz[u] += siz[v]; f[u] += f[v] + siz[v];
	}
}
void dfs2(int u, int pre) {
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].v; if(v == pre) continue;
		f[v] = f[u] + n - 2 * siz[v];
		if(sum < f[v]) sum = f[v], id = v; dfs2(v, u);
	}
}
/*----------------------------------------函数*/
int main() {
	n = read();
	for(int i = 1; i < n; i++)
	{
		int x = read(), y = read();
		add_edge(x, y); add_edge(y, x);
	}
	dfs1(1, 0); dfs2(1, 0); printf("%d", id);
	return 0;
}

\(bzoj3037\) 创世纪

给定一个 \(n\) 个点 \(n\) 条边的图 每个点有一个出边 在图中删去若干点 使所有未删去的点至少有一条入边来自删去的点 求选取的点的最小数量

注意到这是一个基环内向树 题中每个点的出边都为 \(1\) 这是一个基环树森林 考虑反向建边 对于每一个点 该点删 或者是该点至少一个儿子被删

\(f_{i, 0/1}\) 表示到以 \(i\) 点时 以 \(i\) 点为根的子树中 \(i\) 点 \(1\) 删与 \(0\) 不删至少需要保留多少个点

删掉一条边 \(x \to y\) 图变为树 如果 \(x\) 不保留 删掉这条边不影响答案 否则 \(x\) 一定保留且 \(f_{y, 0}\) 初始化为 \(0\)

两次 \(dp\) 取最优

我推的 \(dp\) 式子 :

\(f_{u, 1} = \sum \max\{f_{v, 0}, f_{v, 1}\}\)

\(f_{u, 0} = \sum \max\{f_{v, 0}, f_{v, 1} \} + d + 1\)

\(d = \max\{f_{v, 1} - f_{v, 0} \}\)

状态不太一样 不清楚正确性

给出的式子

\(f_{u, 0} = \sum \max \{f_{v, 0}, f_{v, 1} \}\)

\(f_{u, 1} = \max \sum_{v \ne s} \max\{f_{v, 0}, f_{v, 1} \} + f_{s, 0} + 1\)

贪心解法

从下往上贪 不选叶子 选父亲 最后环上的点选一半

\(Painbow\) \(Road\)

给出一棵树 每条边有颜色 问从哪些点出发不存在一条包含两条同色边的路径

先 \(dp\) 从每个点向下的子树内是否存在这样的路径

然后再从上往下 \(dp\) 一遍 更新所有点向父节点走是否存在非法路径

注意到若一个点至少有两条相邻的同色边 则同色边指向的子树内的点都不可行

将这样的点标为关键点 这样的边断掉 若最后所有关键点都在一个联通块内则可行 联通块内的所有点都是可行解

\(Hanmiltonian\) \(Spanning\) \(Tree\)

有一个 \(n\) 个点的完全图 每条边的权值为 \(y\) 选择一个生成树 将所有边权改为 \(x\) 求最短的哈密顿回路

哈密顿回路

一个回路通过图中每一个顶点一次 称这个环为哈密顿回路

若 \(x \geq y\) 特判菊花图 否则一定有一种方案只走 \(y\) 边

若 \(x < y\) 则要在树上选尽可能多的边 使得每个点的度数最多为 \(2\) 树形 \(dp\)

void dfs(int u) {
    for(int i = 1; i <= k; i++) dfs(s[i]);
    for(int i = 1; i <= k; i++)
    {
        int g[3];
        g[0] = f[u][0]; g[1] = f[u][1]; g[2] = f[u][2];
        f[u][0] = g[0] + Max(f[s[i]][0], f[s[i]][1], f[s[i]][2]);
        f[u][1] = Max(g[1] + Max(f[s[i]][0], f[s[i]][1], f[s[i]][2]), g[0] + Max(f[s[i]][0], f[s[i]][1]) + 1);
        f[u][2] = Max(g[2] + Max(f[s[i]][0], f[s[i]][1], f[s[i]][2]), g[1] + Max(f[s[i]][0], f[s[i]][1]) + 1);
    }
}
ans = Max(f[1][0], f[1][1], f[1][2]);

或者贪心求树的最小路径覆盖

\(Zublicanes\ and\ Mumocrates\)

略

\(treecnt\)

给定一棵 \(n\) 个点的数 从 \(1\) 到 \(n\) 标号 选择 \(k\) 个点 需要选择一些边使得这 \(k\) 个点通过选择的边联通 目标是使得选择的边数最少

计算对于所有选择 \(k\) 个点的情况最小选择边数的总和为多少

枚举每条边 若这条边两侧都有点被选中 对答案贡献 \(1\) 统计情况时补集转化 求全部 \(k\) 个点都在某一边时的情况数 计算组合数

预处理阶乘和组合数要用的逆元

深搜对于每个点统计答案

/*
  Time: 5.4
  Worker: Blank_space
  Source: treecnt
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, k, f[B], siz[B], ans;
struct edge {int v, nxt;} e[B << 1];
int head[B], ecnt;
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
int power(int a, int b, int res = 1) {for(; b; a = a * a % mod, b >>= 1) if(b & 1) res = res * a % mod; return res;}
int C(int n, int m) {return n < m ? 0 : f[n] * power(f[m], mod - 2) % mod * power(f[n - m], mod - 2) % mod;}
void dfs(int u, int pre) {
	siz[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].v; if(v == pre) continue;
		dfs(v, u); siz[u] += siz[v];
		ans = ((ans + C(n, k) - C(siz[v], k) - C(n - siz[v], k)) % mod + mod) % mod;
	}
}
/*----------------------------------------函数*/
signed main() {
	n = read(); k = read(); f[0] = f[1] = 1;
	for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i % mod;
	for(int i = 1; i < n; i++)
	{
		int x = read(), y = read();
		add_edge(x, y); add_edge(y, x);
	}
	dfs(1, 0); printf("%lld", ans);
	return 0;
}

黑暗料理

给定 \(n\) 个节点的树 有 \(m\) 个特殊位置 任选位置出发与结束 经过 \(m\) 个点中随机给定的 \(k\) 个点的最短路程

求期望经过随机 \(k\) 个点要走的距离 答案对 \(998244353\) 取模

...

小凸玩密室

略

此下 后期补充

状压 \(dp\)

旅行商问题

\(Little\ Pony\ and\ Harmony\ Chest\)

\([CDOJ\ 1296]\)

\(Corn\ Fields\ G\)

\(Single\ Elimination\)

树有几多愁

游览计划

斯坦纳树

寿司晚宴

树形 $dp$ $\&$ 状压 $dp$

原文:https://www.cnblogs.com/blank-space-/p/14730468.html

踩
(0)
赞
(0)
   
举报
评论 一句话评论(0)
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!