在做网络流题目的时候,我们时常因为无法正确建图而遗憾错过本应该的AC。
今天通过一些题目,我好好记录一下自己对最大流、最小割的学习感受。
Hdu 1565,题意:
给你一个矩阵,矩阵上每个点上都有一个数字,现在要你在矩阵中取一些数字,使得取得的数字和最大!
规则:相邻的格子不能同时取,即取了一个以后,它四周的数字便不能再取了。
分析:(题目实际就是求一个无向图的最大点权独立集)
这种方格的题目,我们最容易想到二分图,最大流等等。事实上,也确实是用最大流来解二分图。首先建图:我们将行列相加为奇数的点与S相连,容量就为该点的点权,行列相加为偶数的点与T相连,容量同样为该点的点权。然后,所有与S相连的点,将它与它四周连一条边,容量为无穷大。对这样的图求一次最大流,最大流的值即该图的最小点集覆盖,我们最后用所有点权和减去最大流便是我们的答案了(最大点权独立集=所有点权-最小点权覆盖集)
图是这样建好了,可是我们会问,为什么就要这样建图呢?为什么某个点向它四周的点的边容量就要是无穷大?
其实是这样的:求完最大流以后,其实就形成了一个最小割。最小割将图分成了两部分。而这个最小割的几何意义是,完成一次取数方案的最小花费!
我们对残留图进行遍历,对于左边那些点,依然与S相连的点,说明它最终被取了,没有相连的点,即被割了,说明它最终没有被取。我们可以想象,如果某个与S相连的点的边被割了,那么它四周与T相连的那些点与T形成的边便一定被保留了,也就是说想要保留某一个,就必须割去这个点对应的另一边,而最小割割的又恰好是较小的,于是较大的那一个便得到了保存。也可以理解为,割的那条边便是花费,我花费叫小的钱,用来保留较大的点。再看,题目要求某一个点不能与它四周的点同时被取,那么我们就将他们连一条无穷大的边,这样的话,如果这条边的两点都要被取的话(一个与S连,一个与T连),我们就要付出无穷大的代价,这么贵,当然不会取了。
再来看一题HDU 3657,这是2010年成都赛区网赛上的一道题
题意:还是给你一个矩阵,矩阵上有一些数,依然是叫你取数,如果某两个相邻的数被你取了,那么你取得的成绩将会减去某一个代价。也就是说,相邻的点可以取了,只是要额外支付一些代价。并且,还会给你一些点,这些点必须要取到。问你最后取到的最大值。
分析:
依然是二分图的最小割,最后左边与S相连的点说明最终被取到,右边与T相连的点说明最终被取到了。我们先关注一下题目要求一定要取的点,怎么保证最小割一定不会割这些点呢,你可能已经想到了,我们将它与相应的S或者T连无穷大的边,那么最小割就一定不敢割它了。好了,再来处理一下相邻点的问题,上一题中,我们是要求不能取相邻的点,所以我们将他们的边值设为了无穷大,也就是要去他们两个,就必须割断这条边,而割断这条边的话,付出无穷大的代价而已。现在题目要求不是不能取了,只是要付出一些代价,怎么样,想到了什么?是不是将无穷大该成题目要求的代价就OK了?回答是对的!
现在我们建图:
所有行列相加为偶数的点与S相连,所有行列相加为奇数的点与T相连,容量都为他们相应的点权。对于与S相连的所有点,他们向四周连一条代价为2*(x&y)的边,x与y分别为连点的点权。好了,求最大流,最后输出所有点权减去最大流值。
下面是第二题的代码
#include<iostream> #include<string> #include<queue> using namespace std; #define inf 0xfffffff #define min(a,b) a<b?a:b typedef struct node { int v; int f; struct node *next; }node; node *link[3000]; node edge[20000]; int num,S,T,n,m,l,N; int map[51][51]; int di[4][2]={1,0,0,1,-1,0,0,-1}; void add(int u,int v,int f) { edge[num].v=v; edge[num].f=f; edge[num].next=link[u]; link[u]=edge+num++; edge[num].v=u; edge[num].f=0; edge[num].next=link[v]; link[v]=edge+num++; } int h[3000],vh[3000]; int find(int u,int FLOW) { if(u==T) return FLOW; int left=FLOW; int temp=N-1; for(node *p=link[u];p;p=p->next) { if(p->f && h[u]==h[p->v]+1) { int MIN=min(p->f,left); int f=find(p->v,MIN); left-=f; p->f-=f; edge[(p-edge)^1].f+=f; if(!left || h[S]==N) return FLOW-left; } if(p->f && h[p->v]<temp) temp=h[p->v]; } if(FLOW==left) { vh[h[u]]--; if(!vh[h[u]]) { h[S]=N; } else { h[u]=temp+1; vh[h[u]]++; } } return FLOW-left; } int sap() { int ans=0; N=T+1; memset(vh,0,sizeof(vh)); memset(h,0,sizeof(h)); vh[0]=N; while(h[S]<N) { ans+=find(S,inf); } return ans; } int main() { int i,j,k,a,b,sum; freopen("D:\\in.txt","r",stdin); while(scanf("%d%d%d",&n,&m,&l)!=EOF) { S=0; T=n*m+1; sum=0; memset(link,0,sizeof(link)); num=0; for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { scanf("%d",&map[i][j]); sum+=map[i][j]; } } for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if((i+j)%2==0) { add(S,(i-1)*m+j,map[i][j]); for(k=0;k<4;k++) { a=i+di[k][0]; b=j+di[k][1]; if(a>=1 && a<=n && b>=1 && b<=m) { add((i-1)*m+j,(a-1)*m+b,2*(map[i][j]&map[a][b])); } } } else { add((i-1)*m+j,T,map[i][j]); } } } for(i=0;i<l;i++) { scanf("%d%d",&a,&b); if((a+b)%2==0) { add(S,(a-1)*m+b,inf); } else { add((a-1)*m+b,T,inf); } } cout<<sum-sap()<<endl; } return 0; }