火星人侵略地球,他们意图登陆破坏某个地区的兵器工厂。据探子回报,火星人登陆的地区为n*m大小的地域,而且每一个火星人的着陆点坐标已知。
火星人很强悍,只要有一个火星人着陆后能够幸存,他必定能毁坏这片区域的全部兵工厂。为了防止这种情况发生,必须保证在火星人着陆的一瞬间把他们全部同时杀死。
现在防卫队有一个激光枪,开一枪就能把 在同一行(或同一列)着陆的火星人全部杀死。但是这种激光枪的使用是有代价的,把这种激光枪安装到不同行的行首、或者不同列的列首,费用都不同。现在已知把激光枪安装到任意位置的费用,总的花费为这些安装了激光枪的行列花费的乘积。
问怎样安装激光枪才能在杀死所有火星人的前提下费用最少?
本题的模型是显然是一个二分图,并且是二分图中直观的顶点覆盖问题。
首先说说“覆盖”的大致概念:图G的一个顶点覆盖是由一些顶点构成的集合Q∈V(G), 又G中有若干条边的集合P∈E(G),这些边均至少有一个端点在Q内,则P被Q顶点覆盖。
二分图中的顶点覆盖问题是,如果覆盖每个顶点需要付出不同的代价,也可以说是不同的花费,或称为点权(或为容量),那么问题可以描述成,在保证覆盖所有边的情况下,如何使得权和最小。
求二分图顶点覆盖问题,都是转化为最小割问题去求解,转化方法如下:
建超级源S 和超级汇 T,假设二分图两个点集分别为 X 和 Y。X和Y原来的边容量设为INF,将S到X里每个点x连一条边,边权为x的点权,也可以看做覆盖点x的所有出边的花费(W-),将Y里每个点y到T连一条边,边权为y的点权,也可以看做覆盖点y的所有入边的花费(W+)。这样求得最小割容量,即为原问题的解。
其中product不是“产品”的意思,而是“乘积”的意思,英语差的同学建议查字典。
因此为了方便求最大流,应该首先对所有费用(点权)求一次自然对数,把乘法转变为加法。最后再对累加的最小费用求一次exp,就是答案。
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N=5000;
const int M=5000;
const int inf=1<<30 ;
const double esp=1e-8 ;
struct node
{
int u,v,next;
double c ;
}edge[M];
int head[N],pre[N],cur[N],gap[N],dis[N];
int top ;
double min(double a,double b)
{
return a<b ? a:b ;
}
void add(int u ,int v, double c)
{
edge[top].u=u;
edge[top].v=v;
edge[top].c=c;
edge[top].next=head[u];
head[u]=top++;
edge[top].u=v;
edge[top].v=u;
edge[top].c=0;
edge[top].next=head[v];
head[v]=top++;
}
double sap(int s,int t ,int nv)
{
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
int u,v;
double minflow=inf,flow=0;
for(int i = 0 ; i <nv ; i++) cur[i]=head[i] ;
u=pre[s]=s;
gap[s]=nv;
while(dis[s] < nv )
{
loop :
for(int &j=cur[u] ; j!=-1 ; j=edge[j].next)
{
v=edge[j].v ;
if(edge[j].c > 0 && dis[u] == dis[v] +1 )
{
minflow = min(minflow ,edge[j].c) ;
pre[v]=u;
u=v ;
if(v==t)
{ if(minflow < esp) continue ;
for( u =pre[v] ; v!=s ;v=u,u=pre[u])
{
edge[cur[u]].c -= minflow ;
edge[cur[u]^1].c += minflow ;
}
flow += minflow ;
minflow = inf ;
}
goto loop ;
}
}
int mindis=nv ;
for(int i = head[u] ; i!=-1 ; i=edge[i].next)
{
v=edge[i].v ;
if(edge[i].c > 0 && dis[v] < mindis)
{
mindis = dis[v] ;
cur[u]=i ;
}
}
if(--gap[dis[u]]==0) break ;
gap[ dis[u] = mindis +1 ]++ ;
u=pre[u] ;
}
return flow ;
}
int main()
{
int T ,n,m,l,a,b;
double x;
scanf("%d",&T);
while(T--)
{
top = 0 ;
memset(head,-1,sizeof(head)) ;
scanf("%d%d%d",&n,&m,&l) ;
int s=0,t=n+m+1 ;
for(int i = 1 ; i <= n ;i++)
{
scanf("%lf",&x);
add(s,i,log(x));
}
for(int i = 1 ; i <= m ; i++)
{
scanf("%lf",&x);
add(i+n,t,log(x));
}
for(int i = 1 ; i <= l ; i++)
{
scanf("%d%d",&a,&b);
add(a,b+n,inf);
}
double ans = sap(s,t,t+1);
printf("%.4lf\n",exp(ans)) ;
}
return 0;
}