P2774 方格取数问题[最大独立点]

方格取数问题

中文题干
就是说让你在一个方格格里面取数字,然后保证取出的数字和最大。

又是一道让我日常感叹,这也是网络流的题目。
刚开始又以为是一个数学问题。
不说废话,直接说为什么这是一道网络流的题目。
想了挺久的,最后我这样说服自己了,这道题目的意思就是,我们要从一堆数字中拿出一些数字,保证数字的和是最大的,这些数和数之间是有相互的制约关系的。那我们就把他从方格里面抽象出来,一些点,相互制约。如果我们把点权想成边权呢?我们要找一些边,使得这些边的权值之和最大,同时边与边之间有一些制约关系。这不就成了网络流里面的最小割问题了吗?
嗯??? 就成了最小割问题了。好像是的吧。 那怎么建图呢:
首先我们先给棋盘进行染色,黑白相间的那种,为的就是能够明确的分出点与点之间的制约关系。假设第一个格子的点为白色。我们把白色的格子和源点相连接,将黑色的格子和汇点相连接。以上的两种边,流量都是自己点的权值。然后再将相邻的白色格子和黑色格子相连接,流量是无穷大。
这样我们跑完一遍之后,断掉的就是最小的那条割。

图1
上面给出图,此图来自洛谷题解

#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>

#define INF 0x3f3f3f3f
#define len 100010
using namespace std;

struct edge{
    int u, v, nxt, f;
};

edge e[len];
int head[len], hcnt;
int dep[len], cur[len];
int S, T;
int M[110][110], color[110][110];
int dc[4][2] = {{1, 0}, {0, 1}, {0, -1}, {-1, 0}};

void Init (){
    memset (head, -1, sizeof (head));
    memset(cur, 0, sizeof (cur));
    memset(M, 0, sizeof (M));
    hcnt = 0;
}

void adde(int u, int v, int f){
    e[hcnt].u = u; e[hcnt].v = v; e[hcnt].f = f;
    e[hcnt].nxt = head[u]; head[u] = hcnt ++;
    e[hcnt].u = v; e[hcnt].v = u; e[hcnt].f = 0;
    e[hcnt].nxt = head[v]; head[v] = hcnt ++;
}

bool bfs(){
    queue <int> q;
    memset(dep, -1, sizeof (dep));
    dep[S] = 0; q.push(S);
    while (!q.empty()){
        int h = q.front(); q.pop();
        
        for (int i=head[h]; ~i; i=e[i].nxt){
            int v = e[i].v;
            if (dep[v] == -1 && e[i].f > 0){
                dep[v] = dep[h] + 1;
                q.push(v);
                if (v == T) return true;
            }
        }
    }
    return false;
}

int dfs(int x, int mw){
    if (x == T || !mw) return mw;
    int sum = 0;
    for (int i=cur[x]; ~i; i=e[i].nxt){
        cur[x] = i;
        int v = e[i].v;
        if (dep[v] == dep[x]+1 && e[i].f > 0){
            int flow = dfs(v, min(e[i].f, mw));
            if (flow){
                sum += flow;
                mw -= flow;
                e[i].f -= flow;
                e[i^1].f += flow;
                if (!mw) break;
            }
        }
    }
    if (!sum) dep[x] = -1;
    return sum;
}

int dinic(){
    int ans = 0;
    while (bfs()){
        for (int i=S; i<=T; i++) cur[i] = head[i];
        ans += dfs(S, INF);
    }
    return ans;
}

int main(){
    int n, m; scanf("%d%d", &n, &m);
    int res = 0;
    Init();
    S = 0; T = n * m + 1;
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            scanf("%d", &M[i][j]);
            res += M[i][j];
        }
    }
    
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            if (j == 1 && i == 1) color[i][j] = 1;
            else if (j == 1) color[i][j] = 1- color[i-1][j];
            else color[i][j] = 1 - color[i][j-1];
        }
    }//染色
    
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            if (color[i][j]) adde(S, (i-1)*m+j, M[i][j]);
            else adde((i-1)*m+j, T, M[i][j]);
            
            if (color[i][j]){
                
                for (int k=0; k<4; k++){
                    int _x = dc[k][0] + i;
                    int _y = dc[k][1] + j;
                    
                    if (_x<1 || _x>n || _y<1 || _y>m) continue;
                    adde((i-1)*m+j, (_x-1)*m+_y, INF);
                }
            }
        }
    }
    
    int ans = dinic();
    
    printf ("%d\n", res - ans);
    return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值