hdu 5988 Coding Contest 青岛G题 费用流以及一些感想吧

Coding Contest

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1035    Accepted Submission(s): 189


Problem Description
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.
 

Input
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.
 

Output
For each turn of each case, output the minimum possibility that the networks would break down. Round it to 2 digits.
 

Sample Input
  
  
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
 

Sample Output
  
  
0.50
 

Source


题意:给定n个点,m条有向边,每个点是一个吃饭的地方,每个人一盒饭。每个点有S个人,有B盒饭。每条边只能被走c次,每条边上都有电线,第一个人通过的时候,不会破坏电线,从第二个人开始,每次都有概率p破坏掉电线。使得每个人都能吃饭,求最小破坏电线的概率。


解法:每条边有走的次数(流量),每条边走一次发生破坏概率为p(流量1,费用p),容易想到费用流。可是费用流往往是费用相加的,这个是概率,只能相乘。有什么办法,log函数可以把乘除法转换为加减法。所以对每个概率取个log当成费用就行了。

log取底数取个2,然后对每条边的概率值取个对数,跑一次最小费用流,感觉没什么问题,但是会wa,因为概率总是小于1的,而底数是2,这样取log后会变为负数。费用为负,跑出来的费用就会朝更小走,在这个题上会出问题。那么取个负呢,把负变成正,还是会出问题,取负之后最小就变成了最大,跑出来是最大费用,也是会出问题的。

这时候就应该从反方向进行考虑,求踩坏的最小概率,就是求不踩坏的最大概率,1-p后取log,和以上同理,求出了最大费用。取出来还回去后用1减一下就好了。

新建源点s,汇点t,对于S>B的需要人走,从源点连一条流量为S[i]-B[i],费用为0(出门不需要费用)的边过去,add(s,i,S[i]-B[i],0),对于s<b的,add(i,t,B[i]-S[i],0)。

然后还有一个问题,就是第一次踩的时候,不会触发,那么从原有的边中取一条出来,流量1,费用0就好了。

还有一点是,我这个代码是玄学AC,最快一次795MS,但是也T了几次,不是很懂为什么。

这个做法是赛后火山学长给我点了一下,然后我自己YY一下做出来的,没看过网上的题解,也不知道有没有什么更好的做法,这道题的目标只是自己能做出来就行了,也不想去管更好的做法,毕竟管不了了。


///2016.11.13
#include<cstdio>
#include<string.h>
#include<queue>
#include<algorithm>
#define maxm 50000
#define inf 1e8
using namespace std;
const int maxn = 200;
const double eps = 1e-7;
const float EXP = exp(1.0);

int num, p[maxn];    ///邻接表头结点
struct EDGE
{
    int u, v, flow, next;
    double cost;
    EDGE() {}
    EDGE(int u, int v, int flow,double cost, int next): u(u), v(v), flow(flow),cost(cost), next(next) {}
} E[maxm];
int n,m;
int S[maxn],B[maxn]; ///每个点的人数S,饭B
void init()          ///初始化
{
    num = 0;
    memset(p, -1, sizeof p);
}
void add(int u, int v, int flow, double cost)
{
    E[num] = EDGE(u, v, flow, cost, p[u]);
    p[u] = num++;
    E[num] = EDGE(v, u, 0, -cost, p[v]);
    p[v] = num++;
}
int pre[maxn], mi[maxn];
double dis[maxn];
bool inq[maxn];
int s, t;            ///源点汇点
double ans;
int flow;
int que[maxn];
bool spfa()          ///火山哥给的模版,挺快的,我只会用
{
    for(int i = 0; i <=t; i++)
        inq[i] = 0, dis[i] = inf,pre[i]=-1;
     dis[s] = 0, mi[s] = inf, inq[s] = 1;
    int l = 0, r = 1;
    que[l] = s;
    while(l != r)
    {
        int u = que[l++];
        if(l == maxn) l =0;
        inq[u] = 0;
        for(int i = p[u]; i + 1; i = E[i].next)
        {
            int v = E[i].v;
            if(E[i].flow && dis[v] > dis[u] + E[i].cost+eps)
            {
                dis[v] = dis[u] + E[i].cost;
                pre[v] = i;
                mi[v] = min(mi[u], E[i].flow);
                if(!inq[v])
                {
                    inq[v] = 1;
                    que[r++] = v;
                    if(r >= maxn) r -= maxn;
                }
            }
        }
    }
    if(pre[t]==-1) return false;
    flow += mi[t];
    ans += mi[t] * dis[t];
    int u = t;
    for(int i = pre[u]; i + 1; i = pre[E[i].u])
    {
        E[i].flow -= mi[t];
        E[i ^ 1].flow += mi[t];
    }
    return true;
}
double Mincost()
{
    ans = 0, flow = 0;
    while(spfa());
    return ans;
}

int main(void)
{
    int T;
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d%d",&n,&m);
        s = 105,t = 106;
        for(int i = 1;i <= n;i++){
            scanf("%d%d",&S[i],&B[i]);
            if(S[i] > B[i]){     ///人数大于饭数,源点建边出来
                add(s,i,S[i]-B[i],0);
            }
            if(B[i] > S[i]){     ///人数小于饭数,建边到汇点
                add(i,t,B[i]-S[i],0);
            }
        }
        for(int i = 1;i <= m;i++){
            int u,v,f;
            double pp;
            scanf("%d%d%d%lf",&u,&v,&f,&pp);
            add(u,v,1,0);             ///第一次走没有费用
            add(u,v,f-1,-log2(1-pp)); ///流量-1
        }
        double tmp = Mincost();
        printf("%.2f\n",1-pow(2,-tmp));
    }
    return 0;
}




想想这估计是我在hdu做的最后一道题,也是我ACM生涯的最后一道题以及最后一道题解了。青岛现场赛没过这道题,当时根本想到拆边的做法。没想到网络流会是我去的现场赛的银牌题。其实感觉这道题也没这么难想,只怪我网络流那些模型的题没刷得完,使得网络流这些的基本模型都没掌握完,遇到变形就真的手足无措。想辩解一下,又觉得没必要,弱就是弱,不需要解释。对ACM我确实没有那种热爱,即使坐下来认真去做题,也不能总是全神贯注,像做喜欢的事那样去思考和专研。AC确实可以带来非常爽的感觉,但是在这个过程中,常常会做一道题做到扣脑壳也找不出BUG,然后在网上去找ac代码进行对拍,这样ac后带来的快感就降低了很多。总之就是过程当中得到的与失去的不成正比,觉得不亏,但是也不赚。但是我继续做下去,我觉得以我的态度,肯定是不能做好的。

还有后来才意识到的一个问题,就是我发现我大二做过的一些专题训练的题,很多属于银牌题,金牌题,但是那个时候我铜牌水平都没达到,我去那种题感觉是收效甚微的,除了会套个模版什么也不会。我觉得正常的过程应该是对基础代码能力有一个量的训练,因为基础代码能力属于铜牌题,也是银牌和金牌题的功底。没有基础代码能力,后面的都是空谈。也就是基本的各种思维能力都没有,还说个鸡霸算法。然而我跨过了这个过程,去学习一些新算法,我觉得这样是收效甚微的,我觉得我浪费的时间简直不是一点半点。难以想象为什么一个cf1500多分的人会去死命磕割点和桥边双联通点双联通强联通网络流这些。不应该先把刷各种基础题,把分提到1700以上再去考虑学习新算法吗。或者一边学习新算法,一边做大量代码能力的题,在水平不够的情况下,我觉得基础代码能力才是关键。哎,可是我一直没意识到这个问题,意识到的时候已经大三了。后面想努力补回来,我觉得是不行的,因为智商不够,数学不好,又不够勤奋(不然就不会来这个大学了)。不过这届大二的已经意识到了这个问题,哎,我们当年嗯是什么机会都没有,羡慕这届大二的。

虽然只去过一次区域赛,而且我很弱,我很菜,但是加入这个实验室,学到的东西还是很多的。最重要的一点就是见识了世界有多大,虽然没去过更大的舞台,但是已经深深感受到了。比赛的时候,旁边全都是各种985、211,在场的大部分人以后都会成为程序员,我也会程序员(没有其他选择了)。在场的很多也是大学才开始学的,种种表现能看得出他们很多人比我优秀很多很多,我不知道我以后有什么能力可以和他们去竞争,或许根本无法同台,我只是个搬砖的。还有清华clj,杜教,鏼爷就在旁边的旁边,这辈子有幸离大神这么近,本来想去要个签名,总觉得有点不合适,作为一个粉丝去要签名,我觉得是没什么的,但是在赛场上,作为对手(虽然根本没资格,不过站在那里了,我就是这么想的),要签名就有点看不过去。还见识了一件事情,就是偶像的力量。先把自己比作屌丝,某j比作我的偶像,然后看到一个非常漂亮的妹纸,屌丝不好意思与之接近,妹纸偶像某j,去要签名要合照。得到一个体会,偶像轻而易举得到的是屌丝梦寐以求却得不到的。

通过这个实验室,我觉得我视野边开阔了,我总喜欢自嘲自己是辣鸡,旁边人又说你是辣鸡我们是什么。虽然我自嘲辣鸡,但我确实就是一个辣鸡,因为我见识了很大的舞台,知道自己的渺小,不能妄自菲薄。我下三楼去软件实验室了,在今后的过程中,我觉得我不会因为做出一个小的APP,一个网页而觉得我自己多厉害,因为我知道这只是一个很不起眼事情,虽然旁边人可能会觉得我很厉害,但是我知道的是,我以后出去肯定不是和这些人竞争,在他们面前的优越感并没有什么意义。我知道学校以外厉害的人简直数都数不清,他们才是我的目标。加油,我是最胖的!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值