bzoj3597: [Scoi2014]方伯伯运椰子【分数规划+费用流】

22 篇文章 1 订阅
Description

四川的方伯伯为了致富,决定引进海南的椰子树。
方伯伯的椰子园十分现代化,椰子园中有一套独特的交通系统。

现在用点来表示交通节点,边来表示道路。这样,方伯伯的椰子园就可以看作一个有 n+2 个交通节点,m 条边的有向无环国。n+1 号点为入口,n+2号点为出口。每条道路都有 6 个参数:ui ,vi ,ai ,bi ,ci ,di ,分别表示,该道路从 ui 号点通向 vi 号点,将它的容量压缩一次要 ai 的花费,容量扩大一次要 bi 的花费,该条道路当前的运输容量上限为 ci,并且每单位运输量通过该道路要 di 费用。

在这个交通网络中,只有一条道路与起点相连。因为弄坏了这条道路就会导致整个交通网络瘫痪,聪明的方伯伯决定绝不对这条道路进行调整,也就是说,现在除了这条道路之外,对其余道路都可以进行调整。

有两种调整方式:
1. 选择一条道路,将其进行一次压缩,这条道路的容量会下降 1 单位。
2. 选择一条道路,将其进行一次扩容,这条道路的容量会上升 1 单位。
一条道路可以被多次调整。

由于很久以前,方伯伯就请过一个工程师,对这个交通网络进行过一次大的优化调整。所以现在所有的道路都被完全的利用起来了,即每条道路的负荷都是满的(每条道路的流量等于其容星)。

但方伯伯一想到自己的海南椰子会大丰收,就十分担心巨大的运输量下,会导致过多的花费。

因此,方伯伯决定至少进行一次调整,调整之后,必须要保持每条道路满负荷,且总交通量不会减少。

设调整后的总费用是 Y,调整之前的总费用是 X 。现在方伯伯想知道,最优调整比率是多少,即假设他进行了 k 次调整,(X-Y)/K 最大能是多少?

注:总费用 = 交通网络的运输花费 + 调整的花费。

Input

第一行包含二个整数N,M

接下来M行代表M条边,表示这个交通网络
每行六个整数,表示Ui,Vi,Ai,Bi,Ci,Di
接下来一行包含一条边,表示连接起点的边

Output

一个浮点数,保留二位小数。表示答案,数据保证答案大于0

Sample Input

5 10

1 5 13 13 0 412

2 5 30 18 396 148

1 5 33 31 0 39

4 5 22 4 0 786

4 5 13 32 0 561

4 5 3 48 0 460

2 5 32 47 604 258

5 7 44 37 75 164

5 7 34 50 925 441

6 2 26 38 1000 22

Sample Output

103.00

HINT

1<=N<=5000

0<=M<=3000

1<=Ui,Vi<=N+2

0<=Ai,Bi<=500

0<=Ci<=10000

0<=Di<=1000

解题思路:

即是要找一种使平均花费减少最多的流量转移方案。

注意到每改变1的流量,在保证答案最优的情况下代价都是相同的,所以转移多少并不是关键,只讨论单位流量即可。

考虑建图,对于原题中的边(u,v),都可以增加流量,所以从u向v连权值为b+d的边;对于本就有流量的边,可以退流,所以从v向u连权值为a-d的边。

现在只要新图中有一个负环,就有一种合法的转移方案,而一个大小为k的环需要调整k次,所以要求的即是平均权值最小的负环,这就是典型的分数规划题目了,二分+SPFA判负环即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

 const int N=505,M=10005;
 const double eps=1e-4;
 int n,m,S,T,cnt[N];
 int tot,first[N],nxt[M],to[M];
 double w[M],dis[N];
 bool exist[N];
 queue<int>q;

 void add(int x,int y,double z)
 {
     nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
 }

bool SPFA()
{
    for(int i=1;i<=n;i++)exist[i]=0,cnt[i]=0,dis[i]=1e20;
    while(!q.empty())q.pop();
    q.push(S),exist[S]=1,dis[S]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop(),exist[u]=0,cnt[u]++;
        if(cnt[u]>n)return true;
        for(int e=first[u];e;e=nxt[e])
        {
            int v=to[e];
            if(dis[v]>dis[u]+w[e])
            {
                dis[v]=dis[u]+w[e];
                if(!exist[v])q.push(v),exist[v]=1;
            }
        }
    }
    return false;
}

 int main()
 {
     //freopen("lx.in","r",stdin);
     n=getint(),m=getint();S=n+1,T=n+2,n+=2;
     while(m--)
     {
         int x=getint(),y=getint(),a=getint(),b=getint(),c=getint(),d=getint();
         add(x,y,(double)b+d);if(c)add(y,x,(double)a-d);
     }
     double l=0,r=1000;
     while(abs(r-l)>eps)
     {
         double mid=(l+r)/2;
         for(int i=1;i<=tot;i++)w[i]+=mid;
         if(SPFA())l=mid;
         else r=mid;
         for(int i=1;i<=tot;i++)w[i]-=mid;
     }
     printf("%0.2f",r);
     return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值