深入理解最小割的意义

在做网络流题目的时候,我们时常因为无法正确建图而遗憾错过本应该的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;

}

你可能感兴趣的:(理解)