二分图解决方格取数问题(最小割)

二分图

题目:在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。

【方法分析】

首先把棋盘黑白染色,使相邻格子颜色不同,所有黑色格子看做二分图X集合中顶点,白色格子看做Y集合顶点,建立附加源S汇T。设立源点与汇点;求最大流得到最小割的值——也就是对棋盘来说所有违反条件的所有值的最小和;棋盘总和减去该值就得到最大值;

【代码分析】
与最大流的区别只有建图函数的设计
#include<iostream>
#include<stdio.h>
#include<vector>
#include<queue>
#include<string.h>
#define MAXN 1010
#define MAXE 1000010
#define MAXC 1e9
using namespace std;
int s,t,n,m;
struct ad{
	int x,y,C,F;//表示从x到y的容量是C,流量是F
	ad(){x=0,y=0,C=0,F=0;}
	ad(int a,int b,int c,int d){x=a,y=b,C=c,F=d;}
}a[2*MAXE];
vector<int> G[MAXN];//存的是x到某条边的i值,用来找边
int upd[MAXN];//用于更新当前最小的残量
int fa[MAXN];//前驱边的值
int C;
void Add(int x,int y,int c,int i);//建立图的边与边与点的关系
bool BFS();//寻找增广路径
int Edmonds_Karp();//找最大流
void creat_map();
queue<int> que;
void clear(queue<int> &q){
    queue<int> empty;
    swap(empty, q);
}
int main(){
    cin>>m>>n;
    creat_map();
    printf("%d\n",C-Edmonds_Karp());
}
void creat_map(){
    int c;C=0;
    s=0,t=n*m+1;
    int V_temp=0,E_temp=0;
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            scanf("%d",&c);
            C+=c;
            V_temp++;
            if((i+j)%2){
                Add(s,V_temp,c,E_temp);E_temp++;
                if(j>0){Add(V_temp,V_temp-1,MAXC,E_temp);E_temp++;}
                if(j<n-1){Add(V_temp,V_temp+1,MAXC,E_temp);E_temp++;}
                if(i>0){Add(V_temp,V_temp-n,MAXC,E_temp);E_temp++;}
                if(i<m-1){Add(V_temp,V_temp+n,MAXC,E_temp);E_temp++;}
            }
            else{
                Add(V_temp,t,c,E_temp);
                E_temp++;
            }
        }
    }
}
void Add(int x,int y,int c,int i){
    a[i<<1]=ad(x,y,c,0),G[x].push_back(i<<1);
    a[(i<<1)+1]=ad(y,x,0,0),G[y].push_back((i<<1)+1);
}
bool BFS(){
    clear(que);
    memset(upd,0,sizeof(upd));
    upd[s]=MAXC;
    que.push(s);
    while(!que.empty()){
        int x=que.front();
        que.pop();
        int l=G[x].size();
        for(int i=0;i<l;i++){
            ad E=a[G[x][i]];
            if(!upd[E.y]&&E.C>E.F){
                que.push(E.y);
                upd[E.y]=min(upd[x],E.C-E.F);
                fa[E.y]=G[x][i];
            }
        }
        if(upd[t])
            return 1;
    }
    return 0;
}
int Edmonds_Karp(){
    int Flow=0;
    while(BFS()){
        for(int y=t;y!=s;y=a[fa[y]].x){
            a[fa[y]].F+=upd[t];
            a[fa[y]^1].F-=upd[t];
        }
        Flow+=upd[t];
    }
    return Flow;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值