poj 3308 最小点权覆盖(对数乘积)

火星人侵略地球,防止其破坏兵器工厂的激光枪部署策略。问题转化为求解二分图的最小顶点覆盖,进一步转换为最小割问题。通过建立超级源和超级汇,对点权取对数进行最大流计算,最终得到最小费用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

火星人侵略地球,他们意图登陆破坏某个地区的兵器工厂。据探子回报,火星人登陆的地区为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;
	
}

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值