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