hdu 5988 Coding Contest (费用流变形)

本文介绍了一种使用最小费用流算法解决特定概率问题的方法。该问题要求找到每个人都能吃饭的情况下,破坏电线的最小概率。通过将概率转化为对数形式,并调整费用流算法来实现目标求解。

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

题意:给定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就好了。

------------------------------------------***********************************-----------------------------

然而我的模板很是垃圾,一个点到另一个点不能有多条边,然后GG,所以找了别人能过得一份代码当作我的模板

#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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值