给出一个 N * M 的矩阵,每个格放着一个非负数,要求选出一些数,使他们的和最大,要求是有相邻边的格子里的数不能同时选。
先说,我压根没想过这事网络流……因为方格取数(1)是个状态压缩……
看了题解,才明白的:
这个题由于数据范围较大,所以状态压缩过不去,需要用网络流,我重复一遍人家的建图:
我们知道对于普通二分图来说,最大独立点集 + 最小点覆盖集 = 总点数,类似的,对于有权的二分图来说,有:
最大点权独立集 + 最小点权覆盖集 = 总点权和,
这个题很明显是要求 最大点权独立集 ,现在 总点权 已知,我们只要求出来 最小点权覆盖集 就好了,我们可以这样建图,
1,对矩阵中的点进行黑白着色(相邻的点颜色不同),从源点向黑色的点连一条边,权值为该黑色点的权值,
2,从白色的点向汇点连一条边,权值为该白色点的权值,
3,然后,对于每一对相邻的黑白点,从黑点向白点连一条边,权值为无穷大。
最后求最小割(最大流),即为最小点权覆盖集。
因为我们求出的最小割集一定是从那些相邻的黑白点之间的边(也就是不能用的边,因为相邻的数不能同时选)中选出来的,且是最小代价,也就是说从方格中拿掉的数之和尽量小,那么剩下的数之和一定是最大的。
我只能说,神奇的网络流!!!!Orz!!!!
代码:
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define find_min(a,b) a<b?a:b
using namespace std;
const int N = 2550;
const int MAX = 100000;
struct Edge{
int s,e,v;
int next;
}edge[20*N];
int dir[4][2]={-1,0, 1,0, 0,-1, 0,1};
int n,m,e_num,head[N],d[N],sp,tp;
void AddEdge(int a,int b,int c){
edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
edge[e_num].next=head[a]; head[a]=e_num++;
edge[e_num].s=b; edge[e_num].e=a; edge[e_num].v=0;
edge[e_num].next=head[b]; head[b]=e_num++;
}
int judge(int i,int j,int k){
int ii=i+dir[k][0];
int jj=j+dir[k][1];
if(ii>=1 && ii<=n && jj>=1 && jj<=m)return 1;
return 0;
}
int bfs(){
queue <int> q;
memset(d,-1,sizeof(d));
d[sp]=0;
q.push(sp);
while(!q.empty()){
int cur=q.front();
q.pop();
for(int i=head[cur];i!=-1;i=edge[i].next){
int u=edge[i].e;
if(d[u]==-1 && edge[i].v>0){
d[u]=d[cur]+1;
q.push(u);
}
}
}
return d[tp] != -1;
}
int dfs(int a,int b){
int r=0;
if(a==tp)return b;
for(int i=head[a];i!=-1 && r<b;i=edge[i].next){
int u=edge[i].e;
if(edge[i].v>0 && d[u]==d[a]+1){
int x=find_min(edge[i].v,b-r);
x=dfs(u,x);
r+=x;
edge[i].v-=x;
edge[i^1].v+=x;
}
}
if(!r)d[a]=-2;
return r;
}
int dinic(int sp,int tp){
int total=0,t;
while(bfs()){
while(t=dfs(sp,MAX))
total+=t;
}
return total;
}
int main(){
int i,j,k,a;
while(~scanf("%d%d",&n,&m))
{
int sum=0;
e_num=0;
memset(head,-1,sizeof(head));
sp=0; tp=n*m+1;
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
scanf("%d",&a);
sum+=a;
int x=(i-1)*m+j;
if((i+j)%2==0){
AddEdge(sp,x,a);
for(k=0;k<4;k++){
if(judge(i,j,k)==1){//不出界
int y=(i+dir[k][0]-1)*m+(j+dir[k][1]);
AddEdge(x,y,MAX);//这里要注意边的方向,是黑点向白点连边
}
}
}
else{
AddEdge(x,tp,a);
for(k=0;k<4;k++){
if(judge(i,j,k)==1){//不出界
int y=(i+dir[k][0]-1)*m+(j+dir[k][1]);
AddEdge(y,x,MAX);//注意边的方向,和上面的是相反的
}
}
}
}
}
int max_flow=dinic(sp,tp);
printf("%d\n",sum-max_flow);
}
return 0;
}