zoukankan      html  css  js  c++  java
  • POJ 3308 Paratroopers(最小割EK(邻接表&矩阵))

    Description

    It is year 2500 A.D. and there is a terrible war between the forces of the Earth and the Mars. Recently, the commanders of the Earth are informed by their spies that the invaders of Mars want to land some paratroopers in them× n grid yard of one their main weapon factories in order to destroy it. In addition, the spies informed them the row and column of the places in the yard in which each paratrooper will land. Since the paratroopers are very strong and well-organized, even one of them, if survived, can complete the mission and destroy the whole factory. As a result, the defense force of the Earth must kill all of them simultaneously after their landing.

    In order to accomplish this task, the defense force wants to utilize some of their most hi-tech laser guns. They can install a gun on a row (resp. column) and by firing this gun all paratroopers landed in this row (resp. column) will die. The cost of installing a gun in the ith row (resp. column) of the grid yard is ri (resp.ci ) and the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs. Now, your team as a high rank defense group must select the guns that can kill all paratroopers and yield minimum total cost of constructing the firing system.

    Input

    Input begins with a number T showing the number of test cases and then,T test cases follow. Each test case begins with a line containing three integers 1 ≤m ≤ 50 , 1 ≤n ≤ 50 and 1 ≤l ≤ 500 showing the number of rows and columns of the yard and the number of paratroopers respectively. After that, a line withm positive real numbers greater or equal to 1.0 comes where theith number isri and then, a line withn positive real numbers greater or equal to 1.0 comes where theith number isci. Finally,l lines come each containing the row and column of a paratrooper.

    Output

    For each test case, your program must output the minimum total cost of constructing the firing system rounded to four digits after the fraction point.

    Sample Input

    1
    4 4 5
    2.0 7.0 5.0 2.0
    1.5 2.0 2.0 8.0
    1 1
    2 2
    3 3
    4 4
    1 4

    Sample Output

    16.0000

    参考博客:http://blog.csdn.net/lyy289065406/article/details/6784658

    这里用的EK

    题意:

    火星人侵略地球,他们意图登陆破坏某个地区的兵器工厂。据探子回报,火星人登陆的地区为n*m大小的地域,而且每一个火星人的着陆点坐标已知。

    火星人很强悍,只要有一个火星人着陆后能够幸存,他必定能毁坏这片区域的全部兵工厂。为了防止这种情况发生,必须保证在火星人着陆的一瞬间把他们全部同时杀死。

    现在防卫队有一个激光枪,开一枪就能把 在同一行(或同一列)着陆的火星人全部杀死。但是这种激光枪的使用是有代价的,把这种激光枪安装到不同行的行首、或者不同列的列首,费用都不同。现在已知把激光枪安装到任意位置的费用,总的花费为这些安装了激光枪的行列花费的乘积。

    问怎样安装激光枪才能在杀死所有火星人的前提下费用最少?


    题解:

    1、  构造二分图

    构造方法按照上述把“顶点覆盖问题转化为最小割问题”的方法去处理:

    显然取行坐标为二分图的X集合,编号为1~N,点权就是激光炮在第i行射一炮的费用ri;列坐标为二分图的Y集合,编号为N+1~N+M,点权就是激光炮在第j列射一炮的费用cj。

    然后建立超级源S,编号为0,超级汇T,编号为N+M+1。S向X集合每个点都连一条正向弧,边容量为第i点的点权;Y集合每个点都向T连一条正向弧,边容量为第j点的点权。而落有伞兵火星人的区域,表示该位置的x与y是连通的,则从X集合取该点与Y集合的对应点连一条正向弧,边容量为无限大inf。

    X集合每个点到S的反向弧、T到Y集合每个点的反向弧,落有火星人区域的y 到x的反向弧,也都要连上边(这是为了后续的Dinic算法在增广链上调整流量之用),但边容量默认为0,表示不连通。

    2、  此时问题转化为最小割问题,因为图G的最小割的容量,等于其最大流的流量,因此用求最大流的方法去求解。

    但本题数据比较BT,常规求最大流的方法(压入重标法)会TLE,因此只能用相对更高效的Dinic算法。循环:一次BFS对残余图分层,一次DFS在层次图上求一条增广链,调整最大流。不懂Dinic的同学建议先去查阅相关资料,边学边做吧!

    注意:

    1、  double精度的问题。

    本题有一句这样的话:the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs.

    其中product不是“产品”的意思,而是“乘积”的意思,英语差的同学建议查字典。

    因此为了方便求最大流,应该首先对所有费用(点权)求一次自然对数,把乘法转变为加法。最后再对累加的最小费用求一次exp,就是答案。

    而自然对数log是double型的, double精度在15~16位左右,那么本题的无限大inf和最小精度eps的差距就不能超过15位,否则精度问题会把你WA成sb。
        而又由于,eps要开到输出小数位数两倍的原则(本题要求取到4位小数),那么eps在本题中开到1e-8就是很有必要的一件事情,所以相应的inf最多只能开到1e8。(但其实呢,2倍原则一般针对带有乘除法的浮点型问题,所以本题只开到1e-5或者1e-6亦可)
        但注意本题中的另一个性质,取对数,任何数字取对数log之后就会变得很小(别告诉我你不知道O(logN)的算法有多么快),所以这题的inf开的很小就好。(inf开到1e2都能过,说明poj还比较仁慈,没有添加什么超2^100的数据。)

    2、  存储问题。

    本题推荐用邻接链表存储,邻接矩阵不可能不超时的。还有,上面构图时已经提及过了,在把二分图构造为单源单汇网络流时,看似都是只有一个方向的有向边(正向弧),但其实反向弧也要用到的(Dinic算法),因此往链表添加边a->b时,若不顺便添加边b->a,必然会WA。

    3、poj的C++ 和G++的问题。

    对于双精度输出,G++上面要用%f,C++则用%lf,否则WA。



    试试证明,由于m,n都小于50,所以还是第一种用数组存储的时间快

    第一种数组(适用于密集型):

    #include <iostream>
    #include <stdio.h>
    #include <queue>
    #include <cmath>
    #include <string.h>
    
    #define INF 0x3f3f3f3f
    #define FINF 99999999.0
    #define N 110
    int m,n;
    using namespace std;
    
    double cap[N][N];  ///边容量
    double flow[N][N];  ///边实际流量
    int pre[N];   ///记录增广路径
    double res[N];   ///残余网络
    queue <int> que;
    
    void init()
    {
        while(!que.empty())  que.pop(); ///清空队列
        memset(cap,0,sizeof(cap));
        memset(flow,0,sizeof(flow));
    }
    
    void EK(int s)
    {
        double ans=0;
        int t = m+n+1;
        while(true)
        {
            memset(res,0,sizeof(res));
            res[s] = FINF;///源点的残留网络要置为无限大!否则下面找增广路出错
            pre[s] = -1;
            que.push(s);
            ///bfs找增广路径
            while(!que.empty())
            {
                int x = que.front();
                que.pop();
                for(int i=0; i<=t ; i++)
                {
                    if( !res[i] && flow[x][i] < cap[x][i] )
                    {
                        que.push(i);
                        pre[i] = x;
                        res[i] = min(cap[x][i] - flow[x][i], res[x]);///这里类似dp,如果有增广路,那么res[t]就是增广路的最小权
                    }
                }
            }
            if(res[t]==0) break;///找不到增广路就退出
            // printf("%f---",ans);
            for(int k=m+n+1; pre[k]!=-1; k=pre[k])
            {
                flow[ pre[k] ][ k ] += res[t];///正向边加上新的流量
                flow[ k ][ pre[k] ] -= res[t];///反向边要减去新的流量,反向边的作用是给程序一个后悔的机会
            }
            ans += res[t];
        }
        printf("%.4lf
    ",exp(ans));
    }
    int main()
    {
        int cas;
        int a,b;
        double xx[55],yy[55];
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%d",&n,&m,&cas);
            init();
            for(int i=1; i<=n; i++)
            {
                scanf("%lf",xx+i);
                xx[i]=log(xx[i]);
    
            }
            for(int i=1; i<=m; i++)
            {
                scanf("%lf",yy+i);
                yy[i]=log(yy[i]);
            }
            
            while(cas--)
            {
                scanf("%d%d",&a,&b);
                cap[a][b+n] += FINF;
                cap[0][a] = xx[a];
                cap[n+b][n+m+1] = yy[b];
            }
    
            /*
            for(int i=1; i<=n; i++)
            {
                scanf("%lf",xx+i);
                xx[i]=log(xx[i]);
            }
            for(int i=1; i<=m; i++)
            {
                scanf("%lf",yy+i);
                yy[i]=log(yy[i]);
            }
            while(cas--)
            {
                scanf("%d%d",&a,&b);
                cap[a][b+n] += FINF;
                cap[0][a] = xx[a]; ///没想到这样也对,但是在邻接表的时候就出问题了!!! 
                cap[n+b][n+m+1] = yy[b];
             // printf("---a=%d,b=%d %f %f %f***",a,n+b,cap[a][a+b],cap[0][a],cap[a+b][m+n+a]);
            }*/
    
            EK(0); 
        } 
     return 0;
    }



    
    

    第二种用的链表:

    #include <stdio.h>
    #include <iostream>
    #include <queue>
    #include <cmath>
    #include <vector>
    #include <string.h>
    #define maxn 110
    #define INF 0x3f3f3f3f
    #define FINF 99999999.0
    using namespace std;
    
    struct Edge{
        int from,to;
        double cap,flow;
        Edge(int u,int v,double c,double f):from(u),to(v),cap(c),flow(f){};
    };
    
    struct EK{
      vector<Edge> edge;
      vector<int> G[maxn];
      int pre[maxn];   ///记录路径
      double res[maxn] ;  ///记录当前节点能通过的最大流量
    
      void init(int n){
         for(int i=0;i<n;i++) G[i].clear();
         edge.clear();
      }
      void addedge(int from,int to,double cap ){
        edge.push_back(Edge(from,to,cap,0));
        edge.push_back(Edge(to,from,0,0));
        int m=edge.size();
        G[from].push_back(m-2);  ///从G出去的边
        G[to].push_back(m-1);
      }
      double maxflow(int s,int t){
        double ans = 0;
        while(true){
            queue <int> que;
            memset(res,0,sizeof(res));
            res[s] = FINF;///源点的残留网络要置为无限大!否则下面找增广路出错
            //pre[s] = -1;
            que.push(s);
            while(!que.empty()){  ///bfs找增广路径
                int x = que.front();
                que.pop();
                for(int i=0;i<(int)G[x].size() ;i++){
                    Edge e  = edge[ G[x][i] ] ;
                    if( !res[e.to] && e.cap > e.flow ){
                        que.push(e.to);
                        pre[e.to] = G[x][i];
                        res[e.to] = min(e.cap - e.flow , res[x]);///这里类似dp,如果有增广路,那么res[t]就是增广路的最小权
                    }
                }
                if(res[t]) break;  ///找到一条增广路径
            }
            if(res[t]==0) break;///找不到增广路
            for(int u=t;u != s; u= edge[ pre[u] ].from){
                edge[ pre[u] ].flow += res[t];///正向边加上新的流量
                edge[ pre[u]^1 ].flow -= res[t];///反向边要减去新的流量,反向边的作用是给程序一个后悔的机会
            }
            ans += res[t];
        }
        return ans;
      }
    };
    int main(){
        int T,cas,n,m,a,b;
        double xx[55],yy[55];;
        scanf("%d",&T);
        while(T--){
    
    
            scanf("%d%d%d",&n,&m,&cas);
            EK ek;
            ek.init(m+n+1);
    
            for(int i=1;i<=n;i++){
                scanf("%lf",xx+i);
                xx[i]=log(xx[i]);
                ek.addedge(0,i,xx[i]);
            }
            for(int i=1;i<=m;i++){
                scanf("%lf",yy+i);
                yy[i]=log(yy[i]);
                ek.addedge(n+i,n+m+1,yy[i]);
            }
    
    
            while(cas--){
                scanf("%d%d",&a,&b);
                ek.addedge(a,b+n,FINF);
              //  printf("---a=%d,b=%d %f %f %f %f %f %f***",a,n+b,FINF,xx[a],yy[a],ek.edge[ek.edge.size()-6].cap,ek.edge[ek.edge.size()-4].cap,ek.edge[ek.edge.size()-2].cap);
            }
            printf("%.4f
    ",exp( ek.maxflow(0,m+n+1) ) );  
        }
        return 0;
    }



  • 相关阅读:
    form查询相关表
    获取datagrid更新初始值、新值
    数据库约束查询
    强名称工具(来着.NET)
    使用IE插件不能打开的解决
    导入导出报错
    List批量任务多线程执行工具类
    在C#中使用NHibernate框架查询数据
    使用bat文件顺序执行多个应用程序
    用C#实现抽象工厂模式
  • 原文地址:https://www.cnblogs.com/zswbky/p/5432190.html
Copyright © 2011-2022 走看看