zoukankan      html  css  js  c++  java
  • HDU5988/nowcoder 207G

    题目链接:https://www.nowcoder.com/acm/contest/207/G

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5988

    时间限制:C/C++ 2秒,其他语言4秒
    空间限制:C/C++ 262144K,其他语言524288K
    64bit IO Format: %lld
    题目描述
    A coding contest will be held in this university, in a huge playground. The whole playground would be divided into N blocks, and there would be M directed paths linking these blocks. The i-th path goes from the ui-th block to the vi-th block. Your task is to solve the lunch issue. According to the arrangement, there are si competitors in the i-th block. Limited to the size of table, bi bags of lunch including breads, sausages and milk would be put in the i-th block. As a result, some competitors need to move to another block to access lunch. However, the playground is temporary, as a result there would be so many wires on the path.
    For the i-th path, the wires have been stabilized at first and the first competitor who walker through it would not break the wires. Since then, however, when a person go through the i−th path, there is a chance of pi to touch the wires and affect the whole networks. Moreover, to protect these wires, no more than ci competitors are allowed to walk through the i-th path.
    Now you need to find a way for all competitors to get their lunch, and minimize the possibility of network crashing.
    输入描述:
    The first line of input contains an integer t which is the number of test cases. Then t test cases follow.
    For each test case, the first line consists of two integers N (N ≤ 100) and M (M ≤ 5000). Each of the next N lines contains two integers si and bi (si,bi ≤ 200).
    Each of the next M lines contains three integers ui,vi and ci(ci ≤ 100) and a float-point number pi(0 < pi < 1). It is guaranteed that there is at least one way to let every competitor has lunch.
    输出描述:
    For each turn of each case, output the minimum possibility that the networks would break down. Round it to 2 digits.
    示例1
    输入
    1
    4 4
    2 0
    0 3
    3 0
    0 3
    1 2 5 0.5
    3 2 5 0.5
    1 4 5 0.5
    3 4 5 0.5
    输出
    0.50

    题意:

    题目的灵感估计来源于现场赛发餐包的操作……

    现在赛场划分成了 $N$ 个不相交区域,共有 $M$ 条有向路连接两个区域,

    对于每个区域,给出 $s[i],b[i]$ 代表区域内有 $s[i]$ 个人,$b[i]$ 个餐包,一旦某人在本区域内拿不到餐包,就会前往其他区域获取餐包,

    而众所周知,赛场上的路上是有很多电线的,一不小心就会踢到电线,所以现在每条路上都存在这一些电线,

    现在已知,一旦某个选手走过第 $i$ 条有向边,就有 $p[i]$ 的概率踢到电线,进而影响整个电网,不过经过该路径的第一个人是必然不会踢到电线的,同时对于第 $i$ 条边,限制最多 $c[i]$ 个人走过。

    现在,求整个电网被影响的最小概率。

    题解:

    首先,我们考虑既然将来还要深入学习数学相关知识,概率和期望这一块是怎么样都跑不掉的,所以还不如现在趁机好好巩固一下概率论的基础知识……

    考虑每个人踢到电线的概率是相互独立的,我们将某次某个人经过某条边称作一次实验,

    若每次实验踢到电线事件发生的概率相同,则 $n$ 个人踢到电线 $k$ 次的概率服从二项分布,众所周知二项分布的公式为

    $Pleft( {X = k} ight) = C_n^k p^k left( {1 - p} ight)^{n - k}$

    其中 $p$ 即为一次实验中发生踢到电线事件的概率;

    而发生踢到电线这一事件发生 $1,2,3,cdots$ 次均会影响电网,所以总共进行 $n$ 次独立实验后,电网被影响的概率为

    $sumlimits_{k = 1}^n {Pleft( {X = k} ight)} = 1 - Pleft( {X = 0} ight)$

    易知

    $Pleft( {X = 0} ight) = left( {1 - p} ight)^n$

    但是我们知道,电网被影响的概率为

    $Pleft( {X = 1,2, cdots ,n} ight) = 1 - left( {1 - p} ight)^n$

    当然,本题中,每次实验踢电线事件发生概率不一定相同,但是概率的计算方法依然符合上式:

    $P = 1 - prodlimits_{i = 1}^n {left( {1 - pleft[ i ight]} ight)}$

     

    然后需要考虑如何进行计算最小概率,显然本题为费用流题,那么不妨将选手的移动看做流的流动,故:

    1、若第 $i$ 个区域内,选手比餐包多($s[i]>b[i]$),显然选手要流出,因此从源点向每个这样的区域连边,流量上限为 $s[i] - b[i]$(这部分人要流出),费用为 $0$;

    2、若第 $i$ 个区域内,选手比餐包少($s[i]<b[i]$),显然选手要流入,因此从每个这样的区域向汇点连边,流量上限为 $b[i] - s[i]$(要流入这么多人),费用为 $0$;

    3、若第 $i$ 个区域内,选手和餐包一样多($s[i]=b[i]$),这样的区域不用动了,选手老老实实待在自己区域内吃饭就行。

     

    而题目中给出的 $M$ 条有向边,正好是可供选手流动的边,因此对于 $M$ 条有向边中任意一条 $edge(u,v,c,p)$:建立从节点 $u$ 连向节点 $v$ 的有向边,显然流量上限为 $c$,

    而剩下来的费用是比较难搞的,首先,暂时不考虑第一个人经过某条路必然不会踢到电线这件事情,则因为每 $1$ 单位的流量流过该条边,正好就相当于一次实验,

    那么就要贡献 $ imes left( {1 - pleft[ i ight]} ight)$,而 $k$ 个单位的流量流过该条边的贡献即为 $ imes left( {1 - pleft[ i ight]} ight)^k$,不难想到进行取对数操作,就有

    $log left[ {left( {1 - pleft[ 1 ight]} ight)^{k_1 } imes left( {1 - pleft[ 2 ight]} ight)^{k_2 } imes cdots imes left( {1 - pleft[ n ight]} ight)^{k_n } } ight] = k_1 log left( {1 - pleft[ 1 ight]} ight) + k_2 log left( {1 - pleft[ 2 ight]} ight) + cdots + k_n log left( {1 - pleft[ n ight]} ight)$

    这样一来,就可以变成可以套用费用流的形式了,由于考虑到费用都是正数,将 $log left( {1 - pleft[ i ight]} ight)$ 取负,作为每条边的费用值,这样一来,求出来的费用值就是

    $k_1 left[ { - log left( {1 - pleft[ 1 ight]} ight)} ight] + k_2 left[ { - log left( {1 - pleft[ 2 ight]} ight)} ight] + cdots + k_n left[ { - log left( {1 - pleft[ n ight]} ight)} ight]$

    那么,要 $P = 1 - prodlimits_{i = 1}^n {left( {1 - pleft[ i ight]} ight)}$ 越小越好,即要 $log left[ {left( {1 - pleft[ 1 ight]} ight)^{k_1 } imes left( {1 - pleft[ 2 ight]} ight)^{k_2 } imes cdots imes left( {1 - pleft[ n ight]} ight)^{k_n } } ight]$ 越大越好(只要底数大于 $1$),也即 $k_1 log left( {1 - pleft[ 1 ight]} ight) + k_2 log left( {1 - pleft[ 2 ight]} ight) + cdots + k_n log left( {1 - pleft[ n ight]} ight)$ 越大越好,也即 $k_1 left[ { - log left( {1 - pleft[ 1 ight]} ight)} ight] + k_2 left[ { - log left( {1 - pleft[ 2 ight]} ight)} ight] + cdots + k_n left[ { - log left( {1 - pleft[ n ight]} ight)} ight]$ 越小越好。

     

    然后,在考虑第一个人经过某条路必然不会踢到电线这件事情,我刚开始考虑是对MCMF板子动点手脚,每次跑出来的 cost 减去那些第一次走人的边,事实和理论均证明这样是行不通的,

    而费用流的题,一般都是在见图上搞文章,改板子改多了容易出事情……所以,实际上,我们把上面那些 $edge(u,v,c,p)$ 拆成两条边即可,

    第一条 $edge(u,v,1,0)$,第二条 $edge(u,v,c-1,- log(1-p))$,这样一来,最小费用的性质就可以保证第一条边空着必然先跑第一条,然后才考虑第二条。

    所以,网络流,最精妙的,就是建图,少想点改板子的操作,多想点建图的操作,实在不行了再改板子(改最好也不要魔改……很容易就炸了……显然不影响板子正确性的前提下小改即可)。

     

    所以,整道题就是建图后最小费用最大流,求出费用后,再取负再pow回去即可。

     

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=110;
    const int INF=0x3f3f3f3f;
    const double eps=1e-8;
     
    struct Edge{
        int u,v,cap,flow;
        double cost;
    };
    struct MCMF
    {
        int s,t; //源点汇点
        vector<Edge> E;
        vector<int> G[maxn];
        void init(int l,int r)
        {
            E.clear();
            for(int i=l;i<=r;i++) G[i].clear();
        }
        void addedge(int from,int to,int cap,double cost)
        {
            E.push_back((Edge){from,to,cap,0,cost});
            E.push_back((Edge){to,from,0,0,-cost});
            G[from].push_back(E.size()-2);
            G[to].push_back(E.size()-1);
        }
     
        double d[maxn];
        int vis[maxn];
        int aug[maxn],pre[maxn];
        bool spfa(int s,int t,int &flow,double &cost)
        {
            for(int i=s;i<=t;i++) d[i]=INF;
            memset(vis,0,sizeof(vis));
            queue<int> q;
            q.push(s);
            d[s]=0, vis[s]=1, pre[s]=0, aug[s]=INF;
            while(!q.empty())
            {
                int now=q.front(); q.pop();
                vis[now]=0;
                for(int i=0;i<G[now].size();i++)
                {
                    Edge& e=E[G[now][i]]; int nxt=e.v;
                    if(e.cap>e.flow && d[nxt]>d[now]+e.cost+eps)
                    {
                        d[nxt]=d[now]+e.cost;
                        pre[nxt]=G[now][i];
                        aug[nxt]=min(aug[now],e.cap-e.flow);
                        if(!vis[nxt])
                        {
                            q.push(nxt);
                            vis[nxt]=1;
                        }
                    }
                }
            }
            if(d[t]==INF) return 0;
            flow+=aug[t];
            cost+=d[t]*aug[t];
            for(int i=t;i!=s;i=E[pre[i]].u)
            {
                E[pre[i]].flow+=aug[t];
                E[pre[i]^1].flow-=aug[t];
            }
            return 1;
        }
     
        double mincost()
        {
            int flow=0;
            double cost=0;
            while(spfa(s,t,flow,cost));
            return cost;
        }
    }mcmf;
     
    int n,m;
    int s[maxn],b[maxn];
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            mcmf.init(0,n+1);
            mcmf.s=0;
            mcmf.t=n+1;
            for(int i=1;i<=n;i++)
            {
                scanf("%d%d",&s[i],&b[i]);
                if(s[i]>b[i]) mcmf.addedge(mcmf.s,i,s[i]-b[i],0.0);
                if(s[i]<b[i]) mcmf.addedge(i,mcmf.t,b[i]-s[i],0.0);
            }
            for(int i=1;i<=m;i++)
            {
                int u,v,c; double p;
                scanf("%d%d%d%lf",&u,&v,&c,&p);
                if(c<=0) continue;
                mcmf.addedge(u,v,c-1,-log2(1-p));
                mcmf.addedge(u,v,1,0.0);
            }
            printf("%.2f
    ",1-pow(2,-mcmf.mincost()));
        }
    }

    补充一些注意的点:

    首先,要考虑浮点误差,1.00000000000003 > 1.00000000000002 类似这种情况,很有可能是浮点误差造成的,而非真的前者大于后者,所以我们要设定一个精度,不影响到解题的正确性,又可以避免浮点误差。

    其次,考虑取对数时的底数尽量小,这题之前有人说卡long double,其实大概是因为底数取大了,取成了 $10$,这样一来边的费用就会变小,double的精度就可能不够了。

  • 相关阅读:
    20180710
    20180709
    20180706
    20180705
    20180704
    python3.x 函数的参数
    python函数返回值
    Centos7下命令笔记-ls
    CentOS系统内核、操作系统位数以及系统参数查看
    CentOS7 配置光盘iso镜像为本地yum源
  • 原文地址:https://www.cnblogs.com/dilthey/p/9751423.html
Copyright © 2011-2022 走看看