题目大意:在一个网格上会出现一些火星人,需要消灭他们,因此要在某些行(或列)安装激光枪,并且该激光枪只能杀死该行(或列)的火星人,在某列(或行)安装一个激光枪会产生花费,总的费用为这些费用的乘积,求把所有火星人都杀死的最小总花费。
相当于选一些行或列覆盖所有图中的火星人顶点,若把火星人视为边,行和列视为顶点,则可以这样构图:引入源汇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;
}