bzoj 3597: [Scoi2014]方伯伯运椰子 (01分数规划+spfa)

题目描述

传送门

题解

先考虑如何得到最小的费用。
在原图的基础上加边。
x->y 容量为c,费用为d.
如果是扩容的话x->y 容量为inf,费用为b+d
如果是压缩的话y->x 容量为c,费用为a-d
这个图上跑最小费用最大流一定会得到最小费用,因为与起点相邻的边不做改变所以,最终的总流量也不变。
考虑最大化 XYk ,设新加入的边得到的总费用为 wi ,总流量为 limi
用01分数规划,将上面的式子变形。
XYk>=z
X(X+wi)>=kz
wi>=limiz
wi+limiz<=0
根据消圈定理:某个流f是同流量中的最小费用流,当且仅当f的残余网络中不存在负圈
我们把流量改成 wi+mid ,用spfa判断负环即可。

代码

#include
#include
#include
#include
#include
#include
#define N 30013
#define eps 1e-5
#define inf 1000000000
using namespace std;
double len[N],dis[N];
int cnt[N],can[N],nxt[N],point[N],v[N],tot,n,m;
bool pd;
void add(int x,int y,double z)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; len[tot]=z;
}
void spfa(int x,double mid)  
{  
    can[x]=1;  
    for (int i=point[x];i;i=nxt[i])  
     if (dis[v[i]]>dis[x]+len[i]+mid) {  
        dis[v[i]]=dis[x]+len[i]+mid;  
        if (can[v[i]]==1) {  
            pd=true;  
            return;  
         }  
        spfa(v[i],mid);  
        if (pd) return;  
     }  
    can[x]=0;  
} 
bool check(double x)  
{  
    pd=false;
    memset(can,0,sizeof(can));  
    memset(dis,0,sizeof(dis));  
    for (int i=1;i<=n+2;i++) {   
        spfa(i,x);  
        if (pd) return true;  
    }   
    return false;  
}  
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    double l=0,r=0;
    for (int i=1;i<=m;i++) {
     int x,y; double a,b,c,d;
     scanf("%d%d%lf%lf%lf%lf",&x,&y,&a,&b,&c,&d);
     r+=c*d;
     if (x==n+1) continue;
     add(x,y,b+d);
     if (c) add(y,x,a-d);
    }
    double ans=0;
    while (r-l>eps){
        double mid=(l+r)/2;
        if (check(mid)) ans=max(ans,mid),l=mid+eps;
        else r=mid-eps;
    }
    printf("%.2lf\n",ans);
}

你可能感兴趣的:(图论,01分数规划)