zoj 2874 Paratroopers 网络流 最小割

         题目大意:在一个网格上会出现一些火星人,需要消灭他们,因此要在某些行(或列)安装激光枪,并且该激光枪只能杀死该行(或列)的火星人,在某列(或行)安装一个激光枪会产生花费,总的费用为这些费用的乘积,求把所有火星人都杀死的最小总花费。   

         相当于选一些行或列覆盖所有图中的火星人顶点,若把火星人视为边,行和列视为顶点,则可以这样构图:引入源汇s,t,对每个行节点,连接s与行节点,容量为在该行建立激光器的费用,连接列节点与汇点t,容量为在该列建立激光器的费用,若在原网格中点(i,j)上有一个火星人,则连接i,j,容量为无穷大INF,则最小割容量即为最小费用。

         下面简述一下原因,将原图建立成二分图后,对于图中任何一条边,要么在选行节点,要么选列节点,所以可行方案是一个该二分图的点覆盖集,且选某个点就产生花费,因此即求最小点权覆盖集。

         最小点权覆盖一定是极小点覆盖集,但不一定是最小点覆盖集,联想二分图匹配的网络流算法,匹配限制在点,而点覆盖限制在边,最大流和最小割是对偶问题,对偶往往将问题的性质由边转换为点,由点转换为边,所以可以尝试最小割,因此使用上述建图方法,因为对于任意图中一条路径都是s--->u---->v---->t的形式,割的性质是s与t不连通,又因为边(u,v)为无穷大,所以(u,v)不在割中,则条件简化为(s,u)(v,t)中至少有一条边在割中,正好与点覆盖的限制条件相同:每条边(u,v)至少选中一个顶点,最小点权覆盖的目标是最小化点权之和,正好与最小割的目标相同。

          另外,因为总花费为所有花费的乘积,因此现将花费变成他的对数,再求和后exp即可。

#include <stdio.h>
#include <string.h>
#include <math.h>
#define MAX 110
#define INF 10000000
struct node
{
	double c,f;
}map[MAX][MAX];
int pre[MAX],queue[MAX];
int s,t;
int bfs()
{
	int i,cur,qs,qe;
	memset(pre,-1,sizeof(pre));
	pre[s]=s;
	qs=0;qe=1;
	queue[qs]=s;
	while(qs<qe)
	{
		cur=queue[qs++];
		for(i=0;i<=t;i++)
		{
			if(pre[i]==-1&&map[cur][i].c-map[cur][i].f>0)
			{
				queue[qe++]=i;
				pre[i]=cur;
				if(i==t)
					return 1;
			}
		}
	}
	return 0;
}

double maxflow()
{
	double max=0,min;
	int i;
	while(bfs())
	{
		min=INF;
		for(i=t;i!=s;i=pre[i])
			if(map[pre[i]][i].c-map[pre[i]][i].f<min)
				min=map[pre[i]][i].c-map[pre[i]][i].f;
		for(i=t;i!=s;i=pre[i])
		{
			map[pre[i]][i].f+=min;
			map[i][pre[i]].f-=min;
		}
		max+=min;
	}
	return max;
}

int main()
{
	int i,j,n,m,l,r,cc,w;
	double c;
	scanf("%d",&w);
	while(w--)
	{
		memset(map,0,sizeof(map));
		scanf("%d%d%d",&n,&m,&l);
		s=0;t=n+m+1;
		for(i=1;i<=n;i++)
		{
			scanf("%lf",&c);
			map[s][i].c=log(c);
		}
		for(i=1;i<=m;i++)
		{
			scanf("%lf",&c);
			map[i+n][t].c=log(c);
		}
		for(i=1;i<=l;i++)
		{
			scanf("%d%d",&r,&cc);
			map[r][n+cc].c=INF;
		}
		printf( "%.4lf\n",exp( maxflow() ) );
	}
	return 0;
}

	


 

 

 

 

你可能感兴趣的:(zoj 2874 Paratroopers 网络流 最小割)