深入理解最小割的意义

在做网络流题目的时候,我们时常因为无法正确建图而遗憾错过本应该的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;
}
原文地址:https://www.cnblogs.com/ka200812/p/2123240.html