最大权闭合子图进阶

\(\quad\)上一篇博客 中简要地讲解了最大权闭合子图。在这一篇博客里,我希望能够写一下最大权闭合子图的一些拓展应用。
\(\quad\)P2774 方格取数问题 为例。

有一个 \(m\)\(n\) 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。

\(\quad\) 这一道题有收益元素,却没有代价元素;关系限制也与一般的最大权闭合子图不一样。最大权闭合子图的关系限制的通式是\(i\),则一定 \(j\),而这道题的形式却是\(i\),则一定不 \(j\)。然而,把棋盘像国际象棋那样染色,我们却可以发现,若取一个白格,则一定不取它周围的黑格,这样就得到了最大权闭合子图的三要素:

  1. 收益元素:白格
  2. 代价元素:不取黑格
  3. 限制关系:若(取白格),则一定(不取周围的黑格)。
#include <cstdio>
#include <cstring>

#define index ((i - 1) * n + j)

inline int read(void){
    int res = 0; char ch = std::getchar();
    while(ch < '0' || ch > '9')
        ch = std::getchar();
    while(ch >= '0' && ch <= '9')
        res = res * 10 + ch - 48, ch = std::getchar();
    return res;
}

const int MAXN = 1e2 + 19, INF = 0x3f3f3f3f;

struct Queue{
    int q[MAXN * MAXN << 3], head, tail;
    int& operator[](const int& __x){
        return q[__x];
    }
};

struct Edge{
    int to, next, c;
}edge[MAXN * MAXN << 3];

int cnt = -1, head[MAXN * MAXN];

inline void add(int from, int to, int c){
    edge[++cnt].to = to;
    edge[cnt].c = c;
    edge[cnt].next = head[from];
    head[from] = cnt;
}

int m, n, sq[MAXN][MAXN], ans;
int s = 0, t;

int dep[MAXN * MAXN];

int bfs(void){
    static Queue q; q[q.head = q.tail = 0] = s;
    std::memset(dep, 0, sizeof(dep)); dep[s] = 1;
    while(q.head <= q.tail){
        int node = q[q.head++];
        for(int i = head[node]; i != -1; i = edge[i].next)
            if(!dep[edge[i].to] && edge[i].c)
                dep[edge[i].to] = dep[node] + 1, q[++q.tail] = edge[i].to;
    }
    return dep[t];
}

inline int min(const int& a, const int& b){
    return a < b ? a : b;
}

int dfs(int node, int flow){
    if(node == t || !flow)
        return flow;
    int stream = 0, f;
    for(int i = head[node]; i != -1; i = edge[i].next)
        if(dep[edge[i].to] == dep[node] + 1 && (f = dfs(edge[i].to, min(flow, edge[i].c)))){
            flow -= f, stream += f;
            edge[i].c -= f, edge[i ^ 1].c += f;
            if(!flow)
                break;
        }
    return stream;
}

int dinic(void){
    int flow = 0;
    while(bfs())
        flow += dfs(s, INF);
    return flow;
}

int main(){
    std::memset(head, -1, sizeof(head));
    m = read(), n = read();
    t = m * n + 1;
    for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j)
            ans += sq[i][j] = read();
    for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j)
            if((i + j) & 1){
                add(s, index, sq[i][j]), add(index, s, 0);
                if(i > 1)
                    add(index, index - n, INF), add(index - n, index, 0);
                if(i < m)
                    add(index, index + n, INF), add(index + n, index, 0);
                if(j > 1)
                    add(index, index - 1, INF), add(index - 1, index, 0);
                if(j < n)
                    add(index, index + 1, INF), add(index + 1, index, 0);
            }
            else
                add(index, t, sq[i][j]), add(t, index, 0);
    ans -= dinic();
    printf("%d\n", ans);
    return 0;
}

\(\quad\) 通过把取黑格转换为不取黑格,我们巧妙地构造了一个最大权闭合子图。
\(\quad\) 再看另一道题 P2598 [ZJOI2009]狼和羊的故事

\(n\times m\) 的棋盘上有三种格子:狼,羊,空。请沿着格子的边缘建立最少的篱笆,使得任意两个羊和狼既没有公共边也无法通过空地相连。

\(\quad\) 找到关系限制:若 \(i\) 是一个狼领地的边,\(j\) 是一个羊领地的边,且 \(i\)\(j\) 经过若干(可以是零)块空地相连,那么\(i\) 不建篱笆,\(j\) 就一定建篱笆。收益元素是:\(i\) 不建篱笆,代价元素是:\(j\) 建篱笆。这样就转换为最大权闭合子图了。
\(\quad\) 其实以上两题还有别的理解方式,我只是希望能用一个形式化的模板概括它们。

猜你喜欢

转载自www.cnblogs.com/natsuka/p/12322696.html