P2774 方格取数问题【二分图点权最大独立集(最小割)】

题目描述
在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。
输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。
输出格式:
程序运行结束时,将取数的最大总和输出
输入样例#1:
3 3
1 2 3
3 2 3
2 3 1
输出样例#1:
11
说明
m,n<=100

分析:
问题就是割掉一些相邻的点,点与其相邻点的这条边要被割掉(最小割)。问题转化为求最小割,最小割=最大流。换种思考从图中就是挑出一些点,这些点之间没有边(不相邻),叫做最大独立集。现在问题就是,割掉一些边,构成点权最大独立集,反面考虑就是:最大和 = 总和 - 最小割(最大流)。

这里写图片描述

构造二分图模型,化分成两个集合。如何划分集合?同一集合内不存在边,两个集合之间建边。
建图:
1,i+j是奇数为a集合,i+j是偶数为b集合,根据相邻边进行建图(点二维换一维);
2,S到a集合权值为点权,a集合到b集合权值为INF,b集合到T权值为点权;
3,所求最大流即为最小割;
$ps:
1、二分图中最小顶点覆盖等于最大匹配数;
2、二分图中最小边覆盖=顶点数-最小顶点覆盖(最大匹配);
3、二分图中最大独立集+最小顶点覆盖(最大匹配)=顶点数;$

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
typedef long long LL;
using namespace std;

const int MAXN = 20000 + 50;
const int INF = 1e8;
int head[MAXN], dist[MAXN], vis[MAXN];
int cur[MAXN];
int n, m;
int top = 0;

struct Edge {
    int to, cap, flow, next;
}edge[MAXN * 20];

void init() {
    top = 0;
    memset(head, -1, sizeof(head));
    memset(vis, 0, sizeof(vis));
}

void addedge(int a, int b, int c) {
    Edge E1 = {b, c, 0, head[a]};
    edge[top] = E1;
    head[a] = top++;
    Edge E2 = {a, 0, 0, head[b]};
    edge[top] = E2;
    head[b] = top++;
}

bool BFS(int st, int ed) {
    memset(dist, -1, sizeof(dist));
    memset(vis, 0, sizeof(vis));
    queue<int> que;
    que.push(st);
    vis[st] = 1;
    dist[st] = 0;
    while(!que.empty()) {
        int u = que.front();
        que.pop();
        for(int i = head[u]; i != -1; i = edge[i].next) {
            Edge E = edge[i];
            if(!vis[E.to] && E.cap > E.flow) {
                dist[E.to] = dist[u] + 1;
                vis[E.to] = 1;
                if(E.to == ed) return true;
                que.push(E.to);
            }
        }
    }
    return false;
}

int DFS(int x, int a, int ed) {
    if(x == ed || a == 0) return a;
    int flow = 0, f;
    for(int& i = cur[x]; i != -1; i = edge[i].next) {
        Edge& E = edge[i];
        if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(a, E.cap - E.flow), ed)) > 0) {
            E.flow += f;
            edge[i^1].flow -= f;
            flow += f;
            a -= f;
            if(a == 0) break;
        }
    }
    return flow;
}

int Maxflow(int st, int ed) {
    int flow = 0;
    while(BFS(st, ed)) {
        memcpy(cur, head, sizeof(head));
        flow += DFS(st, INF, ed);
    }
    return flow;
}

int arr[210][210];
int main()
{
    init();
    scanf("%d %d", &m, &n);
    int sum = 0, tt = n * m + 1;
    for(int i = 1;i <= m; ++i) {
        for(int j = 1; j <= n; ++j) {
            scanf("%d", &arr[i][j]);
            sum += arr[i][j];
        }
    }
    for(int i = 1; i <= m; ++i) {
        for(int j = 1; j <= n; ++j) {
            if((i + j) % 2 == 0) addedge(0, (i - 1) * n + j, arr[i][j]);
            else addedge((i - 1) * n + j, tt, arr[i][j]);
            if((i + j) % 2 == 0) { //分集合这一点我被坑到了,用二维判断不要用一维
                if(arr[i][j + 1]) addedge((i - 1) * n + j, (i - 1) * n + j + 1, INF);
                if(arr[i][j - 1]) addedge((i - 1) * n + j, (i - 1) * n + j - 1, INF);
                if(arr[i + 1][j]) addedge((i - 1) * n + j, (i - 1) * n + j + n, INF);
                if(arr[i - 1][j]) addedge((i - 1) * n + j, (i - 1) * n + j - n, INF);
            }
        }
    }
    int ans = Maxflow(0, tt);
    printf("%d\n", sum - ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36368339/article/details/80621545