二分图
题目:在一个有 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;
}