zoukankan      html  css  js  c++  java
  • POJ 3308 Paratroopers

    问题:二分图的最小点权覆盖集
    思路:
    1.构造二分图:分别把行、列的编号看成集合X={r1,r2...}、Y={c1,c2...},把每个人所在的位置(ri,ci)连线ri 、ci;这样就构成了一个二分图,点为行、列的顺序编号1~r+c,边则是人所在的位置(可以代表这个人)。


    2.构造二分网络流图:分别添加源点S、汇点T,建立S->X的各个边,容量为该行ri的费用,反向为0;建立Y ->T的各个边,容量为该列ci的费用,反向为0;把所有X->Y的边的容量设为无穷大,反向为0。


    3.求最大流,有时还需要求出最小割中的各个割边的容量(或者是对应的点ri or ci)。

    注意:product在这里译为“乘积”,所以本题求的是各个cost的乘积的最小值,那么需转换成求最小和;建图
    时流量为log(cost)即对各个cost求自然对数;再求出最大流maxflow,最后exp(maxflow)即e^maxflow;
    a*b*c=e^(loga+logb+logc);还有精度的问题,要用double。

    粗略理解:
    为什么所求的最大流就是最小点权?
    对二分图建立网络流图,他用了贪心的思想,对于X、Y中的点xi、yi:
    若为1:1即只有一条边的情况,那么他选择的是S->x和y->T中较小的容量即选择了点权较小的点;
    若为1:m即有多条边的情况,那么他选择的是S->x和y1->T+y2->T+...+yn->T中较小的容量;
    若为m:1即有多条边的情况,那么他选择的是......较小的容量;
    但是真实情况中,不像上边那么单纯,而是各种单纯的各种混合;但关键的一点不变:贪心的思想,选最小 的点权!更重要的是:对于每一种单纯情况的抉择后,不仅较小的点权的值更新为0,那么较大点权的也要 更新(减去较小的点权,这里所说的点权就是网络流图中的容量(S->x、y->T),为了便于说明)!这就保 证了不重选且最小!
    概括地说:对于X,Y中的每一条边(x--y),若想覆盖掉这条边,则必须选择点x、y中之一才能覆盖之,别无他法 !所以肯定选择最小点权的点,并更新这两个点权的值——为了较大点权的点的再与别的点的选择时,必须建立在更新的基础上,不然就重选了!
    为什么所求的各个点一定覆盖所有的边?
    首先对于求完最大流的残留图,最小割的各个割边一定在S->X、S->Y中;如果你说从割边上选择的点不一定能覆盖所有点,也就是存在一条边e(x-y)的两个点x,y都不在割边上,那也就是说存在一个可行流S->x->y->T!而最大流是不存在可行流的!所以.....

    View Code
    #include <stdio.h>
    #include <math.h>
    #include <memory.h>

    #define N 102
    #define M 1202
    #define MAXVAL (1e+6)//本题相当于e^10^6>2^10^6的数字啊

    int r,c,m,s,t;
    int nodevp[N];
    int nodeu[M],next[M],ind;
    double flow[M];

    void addedge(int v,int u,double val)
    {
    nodeu[ind]=u;
    flow[ind]=val;
    next[ind]=nodevp[v];
    nodevp[v]=ind++;
    }

    void getDataAndBuildGraph()
    {
    int i,v,u;
    double cost;

    scanf("%d %d %d",&r,&c,&m); s=0; t=r+c+1;
    memset(nodevp,-1,sizeof(nodevp)); ind=0;
    for(i=1;i<=r;i++)
    {
    scanf("%lf",&cost);
    addedge(s,i,log(cost));
    addedge(i,s,0.0);
    }
    for( ;i<t;i++)
    {
    scanf("%lf",&cost);
    addedge(i,t,log(cost));
    addedge(t,i,0.0);
    }
    for(i=0;i<m;i++)
    {
    scanf("%d %d",&v,&u);
    addedge(v,u+r,MAXVAL);
    addedge(u+r,v,0.0);
    }
    }

    int dist[N],cur[N],pre[N],cnt[N];
    double SAP()
    {
    int i,v,u;
    double minflow,maxflow=0;

    memset(cnt,0,sizeof(cnt));
    memset(dist,0,sizeof(dist));
    memcpy(cur,nodevp,sizeof(nodevp));
    cnt[0]=t+1; v=s;

    while(1)
    {
    for(i=cur[v]; ~i ; )
    {
    u=nodeu[i];
    if(flow[i]>0 && dist[v]==dist[u]+1)
    {
    cur[v]=i; pre[u]=v;
    if(minflow>flow[i]) minflow=flow[i];
    if(u==t)
    {
    maxflow+=minflow;
    while(u)
    {
    u=pre[u];
    flow[cur[u]]-=minflow;
    flow[cur[u]^1]+=minflow;
    }
    minflow=MAXVAL;
    }
    v=u; i=cur[v];
    }
    else i=next[i];
    }

    if(--cnt[dist[v]] == 0) break;
    for(dist[v]=t,i=nodevp[v];~i;i=next[i])
    {
    u=nodeu[i];
    //刚调试到这的时候,我一看就发现少了个条件flow[i]>0,但是我没停止就是想看看,到底是怎么错的...
    // if(dist[v]>dist[u])//好吧,问题就出现在这里!!死循环!!调试用时30min+...
    //好吧,既然已经浪费了很多时间,也不多这一会儿!来,咱们来看看是怎么回事
    //刚开始没问题.....这时是:dist[S]=3,dist[3]=2,dist[7]=1,dist[T]=0; s->3->7->T存在一条增广路!
    //更新完流量后,7->T之间的流量变为0;然后又回到S,仍从S->3这条线找,因为cur[S]记录了S->3边;
    //从3->7,7无路可走,只好重新标号!注意错误就出现在这里:标完号,7的标号仍是1!很显然应该是3,因为
    //7->T走不通!这里没有了这个flow[i]>0的判断,直接把dist[7]又变回了1;然后回到3,3->7,7无路可走,只好
    //重新标号,标完号dist[7]仍是1;然后又回到了3.......就这样死循环!!!!
    //啊! 累死哥们了!这尼玛伤不起,为找这个死循环,费了巨大的脑力(人脑没法跟电脑比!),1h+的时间!!
    if(flow[i]>0 && dist[v]>dist[u])
    dist[v]=dist[u],cur[v]=i;
    }
    dist[v]++; cnt[dist[v]]++;
    if(v==s) { if(dist[s]>t) break; minflow=MAXVAL; }
    else v=pre[v];
    }

    return maxflow;
    }

    //int flag[N];
    //void DFS(int v)
    //{
    // int i;
    // flag[v]=1;
    // for(i=nodevp[v];~i;i=next[i])
    // if(flow[i]>0 && !flag[nodeu[i]])
    // DFS(nodeu[i]);
    //}

    void solve()
    {
    int cas;
    // double ans;
    for(scanf("%d",&cas); cas ; cas--)
    {
    getDataAndBuildGraph();
    printf("%.4lf\n",exp(SAP()));
    // printf("%.4f\n",SAP());
    // memset(flag,0,sizeof(flag));
    // DFS(s);

    // ans=1.0;
    // for(i=1;i<=r;i++)
    // if(!flag[i]) ans*=cost[i];
    // for( ;i< t;i++)
    // if( flag[i]) ans*=cost[i];
    // printf("%.4lf\n",ans);
    }
    }

    int main()
    {
    freopen("input.txt","r",stdin);

    solve();

    return 0;
    }

    心理历程:
    这尼玛坑死爹了啊!读题的时候都开始蛋疼!
    就是这句:the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs. 我日尼玛啊!坑死爷们了啊!当时怎么读就是觉得不顺!如果翻译成:求最小的消费,那么他为什么要product of their costs呢?怎么都不顺!他们消费的产品?这尼玛啥意思啊?!考!!郁闷!不管了!就当是求最小消费!可是样例输出怎么与我求的对不住?按说是8.000啊,怎么会是16.0000!晕死!难不成是求最大的、那不可能!难不成是cost*行号或列号、那也不对!难道是我行列弄反了、反过来还是8.000啊 !这。。。可能还是那句话没翻译好?我又去翻译那句话。。。就是尼玛看不懂product of their costs!!就 这反复、轮回的去想。。期间我又找人帮我翻译。。等等。。上午结束了。。。没有结果。。。下午继续搞。。8.0000*2=16.0000这个对吧,难道是要求最大流*2。好吧,我就这样做吧,比照着二分图的最小点权覆盖集的模型去建图(因为我就是搜得这个专题)。。那这题。。那不是比那个2125简单啊,不用求最小割的各个割边容量(或者是点)呵,那这建完图整个SAP不就可以了。。好的~想想……敲代码!!敲完,测试,结果错了,看了几遍代码看不出来,无奈很不想调试的去调试了,很快找到了SAP中某地方少了一个条件,代码中有详解,可是我不甘心,想看看它怎么是程序失败。。继续调,调了半个多钟头、终于发现就是少一个条件而造成死循环。啊,累死了,整个过程足有1个小时。。在测试,成功!(真SB,printf("%d",SAP()*2);真尼玛搞笑),好吧submit,果断WA,在提交,依然WA!。。。我日你嘛啊,哥哥实在是木法了,Only search answer!有了一个惊天的发现:product翻译成“乘积”,我考,这、、、还有这个意思?从未听说!!好吧,求乘积!那。。。求最大流,求的是最小和啊!那我求出,最小割中的各个容量的乘积?那不对吧?若a+b+c最小,a*b*c就最小?这不对!那。。。有啥法啊?这尼玛让求乘积最小。。这木法。。这确实木法!Again look answer 发现是:先求各个cost的log,在对求出的和(最大流)exp运算!这尼玛什么情况 ?我想了想。。a*b*c=e^(loga+logb+logc) 。。(⊙o⊙)…对啊,我去,这谁尼玛真牛B呀,求乘积的最小值转化成求log和的最小值!额,也许大家都经常用呵,你懂的,我很水嘛!考,最终在源代码上,建图的时候加了log,在SAP外加个exp!我表示这两个函数貌似是我第一次用!!O(∩_∩)O哈哈~,我很无敌吧!!额,此时的代码已改的面目全非!蛋液碎了一地!

  • 相关阅读:
    tomcat简介与配置
    gitlab简介与配置
    cobbler自动装机服务简介与配置
    Linux中管理员用户与普通用户之间的切换
    kafka 学习
    Linux系统swappiness参数在内存与交换分区之间优化作用
    CentOS7中使用yum安装Nginx的方法
    配置两个Hadoop集群Kerberos认证跨域互信
    Linux shell中2>&1的含义解释
    解决SpringBoot多工程时jar包中注解不能扫描生效问题
  • 原文地址:https://www.cnblogs.com/fornever/p/2415372.html
Copyright © 2011-2022 走看看