【Coel.学习笔记】最小割进阶:最小权覆盖集、最大权独立集

最小权覆盖集

给定一个无向图,点带权。选择某些点,使得点所连边能够包含整张图的所有点,则这个点集叫做点覆盖集;点权之和最小的点覆盖集就叫最小权点覆盖集

对于一般问题来说,这是一个 \(\mathsf{NP}\)-完全问题,即不存在多项式解法的问题;所以我们把无向图限制为二分图。

在二分图中,最大匹配数等于最小点覆盖数等于点数减去最大独立集数,那么当点权全为 \(1\) 时可以用匈牙利算法求解最小权点覆盖集。

当点权任意时怎么做呢?还是按照套路,源点连一边,汇点连另一边,边容量等于权值;中间点连边容量为正无穷,这个问题就可以类比成最小割模型了。

接下来证明可行解与简单割的一一对应关系。首先可以发现,简单割由于不包含正无穷容量边,所以简单割一定可以对应构造得到点覆盖集;反之,我们可以通过点覆盖集发现某些边是否可走,那么按照此方式进行 dfs,就会把整个点集分为两部分:可到达为 \(S\),不可到达为 \(T\)。显然这样的分配方式满足简单割。综上,可行解与简单割一一对应,简单割的容量就等于点覆盖集的点权之和。

[POJ2125] Destroying the Graph

给出一个有向图,每次操作可以选择一个点,删除所有出边或者删除所有入边。求出删除所有边的最小花费方案。

解析:这张图是有向图,看起来和刚才讲的模型没什么关系,但也可以转化一下。
对于一条边 \((u,v)\),为删除它一定要做其中一种操作:删除 \(u\) 的出边,或者删除 \(v\) 的入边。由于每个点都有两个操作,考虑拆点,源点与删入边的操作相连,汇点与删出边的操作相连,中间连上两个操作之间的关系(如 \(u\) 出与 \(v\) 入相连),就换成了一个二分图点集覆盖问题。
此外这道题要求输出方案,也就是点覆盖集的方案。这个其实很简单,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;
}

最大权独立集

还是二分图,上面提到过点权全为 \(1\) 时最大权独立集等于点数减去最小权点覆盖数。那点权不为 \(1\) 呢?这时就有最大权独立集等于点权之和减去最小权点覆盖集

说了这么久,什么是独立集?对于一个无向图,选取某些点使得两点之间没有直接连边,那么这个点集就是一个独立集。最大权独立集就是点权之和最大的独立集。最大权独立集问题同样是一个 \(\mathsf{NP}\)-完全问题,所以还是只能在二分图上考虑。

证明一下最大权独立集与最小权覆盖集之间的对应关系。先看点覆盖集对应独立集。运用反证法,假设点覆盖集的补集不是独立集,那么在独立集中一定存在两点 \(u,v\) 使得它们之间有连边,反之点覆盖集就不会包含这两个点,与定义矛盾。再看独立集对应点覆盖集,同样使用反证法,假设独立集的补集不是点覆盖集,那么一定存在 \(u,v\) 使得这两个点都不在点覆盖集中,那么这两个点一定在独立集中,同样与定义矛盾。

至此,我们证明了最大权独立集等于点权之和减去最小权点覆盖集,把问题转化为了上一个模型。

王者之剑 / 网络流 24 题:方格取数问题

洛谷传送门

给出一个 \(n \times m\) 网格,每个格子上有一个价值 \(v_{i,j}\) 的宝石,从 0 秒开始,起点任意。以下操作,在每秒内按顺序执行。

  1. 若第 \(i\) 秒开始时在 \((x,y)\),则可以拿走 \((x,y)\) 上的宝石。
  2. 在偶数秒时(\(i\) 为偶数),则周围上下左右 \(4\) 格的宝石将会消失。
  3. 若第 \(i\) 秒开始时在 \((x,y)\),则在第 \((i+1)\) 秒开始前,可以马上移动到相邻的格子 \((x+1,y),(x-1,y),(x,y+1),(x,y-1)\) 或原地不动 \((x,y)\)
    求最多能得到多大总价值的宝石。

解析:本题存在几个性质:

  1. 只能在偶数秒拿到宝石,奇数秒无法拿到宝石。这是显然的,因为偶数秒时周围的宝石全部消失,无论怎么走都不可能拿到;
  2. 所有相邻格子的宝石都不可能同时拿到。同样很显然,因为拿到某一格的宝石时,周围的宝石都已经消失了。

这两个性质和最大权独立集问题很相似了,因为相邻宝石不能拿,所以两个格之间没有直接连边,这个性质和独立集是一样的。

我们按照时间的奇偶性把格子分别染成白格子和黑格子,其中白格子与黑格子相互交错。此时白格子和黑格子就构成了一个二分图,照着最大权独立集的方法做即可。

代码如下(同样省略 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;
}
posted @ 2022-07-13 16:10  秋泉こあい  阅读(175)  评论(0编辑  收藏  举报