【Coel.学习笔记】最小割进阶:最小权覆盖集、最大权独立集
最小权覆盖集
给定一个无向图,点带权。选择某些点,使得点所连边能够包含整张图的所有点,则这个点集叫做点覆盖集;点权之和最小的点覆盖集就叫最小权点覆盖集。
对于一般问题来说,这是一个 -完全问题,即不存在多项式解法的问题;所以我们把无向图限制为二分图。
在二分图中,最大匹配数等于最小点覆盖数等于点数减去最大独立集数,那么当点权全为 时可以用匈牙利算法求解最小权点覆盖集。
当点权任意时怎么做呢?还是按照套路,源点连一边,汇点连另一边,边容量等于权值;中间点连边容量为正无穷,这个问题就可以类比成最小割模型了。
接下来证明可行解与简单割的一一对应关系。首先可以发现,简单割由于不包含正无穷容量边,所以简单割一定可以对应构造得到点覆盖集;反之,我们可以通过点覆盖集发现某些边是否可走,那么按照此方式进行 dfs,就会把整个点集分为两部分:可到达为 ,不可到达为 。显然这样的分配方式满足简单割。综上,可行解与简单割一一对应,简单割的容量就等于点覆盖集的点权之和。
[POJ2125] Destroying the Graph
给出一个有向图,每次操作可以选择一个点,删除所有出边或者删除所有入边。求出删除所有边的最小花费方案。
解析:这张图是有向图,看起来和刚才讲的模型没什么关系,但也可以转化一下。
对于一条边 ,为删除它一定要做其中一种操作:删除 的出边,或者删除 的入边。由于每个点都有两个操作,考虑拆点,源点与删入边的操作相连,汇点与删出边的操作相连,中间连上两个操作之间的关系(如 出与 入相连),就换成了一个二分图点集覆盖问题。
此外这道题要求输出方案,也就是点覆盖集的方案。这个其实很简单,dfs 就好了。
代码如下(省略 dinic 的实现):
// Problem: 有向图破坏
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2327/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Author:Coel
//
// Powered by CP Editor (https://cpeditor.org)
bool vis[maxn];
void dfs(int u) {
vis[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (c[i] && !vis[v]) dfs(v);
}
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
S = 0, T = n * 2 + 1;
memset(head, -1, sizeof(head));
for (int i = 1, w; i <= n; i++) {
cin >> w;
add(S, i, w);
}
for (int i = 1, w; i <= n; i++) {
cin >> w;
add(n + i, T, w);
}
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
add(v, n + u, inf);
}
cout << dinic() << '\n';
dfs(S);
for (int i = 0; i < cnt; i += 2) {
int u = to[i ^ 1], v = to[i];
if (vis[u] && !vis[v]) k++;
}
cout << k << '\n';
for (int i = 0; i < cnt; i += 2) {
int u = to[i ^ 1], v = to[i];
if ((vis[u]) && (!vis[v]))
if (u == S)
cout << v << ' ' << '+' << '\n';
else if (v == T)
cout << u - n << ' ' << '-' << '\n';
}
return 0;
}
最大权独立集
还是二分图,上面提到过点权全为 时最大权独立集等于点数减去最小权点覆盖数。那点权不为 呢?这时就有最大权独立集等于点权之和减去最小权点覆盖集。
说了这么久,什么是独立集?对于一个无向图,选取某些点使得两点之间没有直接连边,那么这个点集就是一个独立集。最大权独立集就是点权之和最大的独立集。最大权独立集问题同样是一个 -完全问题,所以还是只能在二分图上考虑。
证明一下最大权独立集与最小权覆盖集之间的对应关系。先看点覆盖集对应独立集。运用反证法,假设点覆盖集的补集不是独立集,那么在独立集中一定存在两点 使得它们之间有连边,反之点覆盖集就不会包含这两个点,与定义矛盾。再看独立集对应点覆盖集,同样使用反证法,假设独立集的补集不是点覆盖集,那么一定存在 使得这两个点都不在点覆盖集中,那么这两个点一定在独立集中,同样与定义矛盾。
至此,我们证明了最大权独立集等于点权之和减去最小权点覆盖集,把问题转化为了上一个模型。
王者之剑 / 网络流 24 题:方格取数问题
给出一个 网格,每个格子上有一个价值 的宝石,从 0 秒开始,起点任意。以下操作,在每秒内按顺序执行。
- 若第 秒开始时在 ,则可以拿走 上的宝石。
- 在偶数秒时( 为偶数),则周围上下左右 格的宝石将会消失。
- 若第 秒开始时在 ,则在第 秒开始前,可以马上移动到相邻的格子 或原地不动 。
求最多能得到多大总价值的宝石。
解析:本题存在几个性质:
- 只能在偶数秒拿到宝石,奇数秒无法拿到宝石。这是显然的,因为偶数秒时周围的宝石全部消失,无论怎么走都不可能拿到;
- 所有相邻格子的宝石都不可能同时拿到。同样很显然,因为拿到某一格的宝石时,周围的宝石都已经消失了。
这两个性质和最大权独立集问题很相似了,因为相邻宝石不能拿,所以两个格之间没有直接连边,这个性质和独立集是一样的。
我们按照时间的奇偶性把格子分别染成白格子和黑格子,其中白格子与黑格子相互交错。此时白格子和黑格子就构成了一个二分图,照着最大权独立集的方法做即可。
代码如下(同样省略 dinic 的实现):
// Problem: P4474 王者之剑
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4474
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author:Coel
//
// Powered by CP Editor (https://cpeditor.org)
const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int tot;
inline int get(int x, int y) { return (x - 1) * m + y; }
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
S = 0, T = n * m + 1;
memset(head, -1, sizeof(head));
for (int i = 1; i <= n; i++)
for (int j = 1, x; j <= m; j++) {
cin >> x;
if ((i + j) % 2 == 1) {
add(S, get(i, j), x);
for (int k = 0; k < 4; k++) {
int x = i + dx[k], y = j + dy[k];
if (x >= 1 && x <= n && y >= 1 && y <= m)
add(get(i, j), get(x, y), inf);
}
} else
add(get(i, j), T, x);
tot += x;
}
cout << tot - dinic();
return 0;
}
本文作者:Coel's Blog
本文链接:https://www.cnblogs.com/Coel-Flannette/p/16474273.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步