洛谷3288 [SCOI2014]方伯伯运椰子

Luogu

BZOJ

分析

很神仙的 $\mathrm{0/1}$ 分数规划。

显然调整后总交通量不变是最佳的,因为不能减少,并且增加的话费用一定会变大。

发现压缩操作很像网络流中的退流,扩充操作很像网络流中的增广

所以对于原图中的每条边 $(u,v,a,b,c,d)$ ,新建两条边:

  • $v\to u$ ,边权为 $a-d$ (如果 $c=0$ ,不要建这条边)。对应压缩
  • $u\to v$ ,边权为 $b+d$ 。对应扩充

发现在这张图跑,经过的边数就是调整次数 $k$ 。

所以调整后的新增费用就是 $\sum w$ 。

二分答案 $mid$ ,如果 $mid$ 合法那么显然有 $mid\geq \frac{X-Y}{k}$ 。

也就是 $mid\times k\geq X-Y=X-(X+\sum w)=-\sum w$ 。

再移一次项,变为 $mid\times k+\sum w>0$ 。

于是将边权变为 $w+mid$ ,然后用 $\mathrm{SPFA}$ 找负环判断是否合法即可。

具体实现及细节见代码。

代码

//It is made by M_sea
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#define re register
using namespace std;

inline int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

const int N=5000+10;
const int M=6000+10;

int n,m;

struct Edge { int v,w,nxt; } e[M];
int head[N];

inline void addEdge(int u,int v,int w) {
    static int cnt=0;
    e[++cnt]=(Edge){v,w,head[u]},head[u]=cnt;
}

double dis[N]; int cnt[N],inq[N];

inline int spfa(int s,double mid) {
    queue<int> Q; Q.push(s); dis[s]=cnt[s]=0,inq[s]=1;
    while (!Q.empty()) {
        int u=Q.front(); Q.pop(); inq[u]=0;
        for (re int i=head[u];i;i=e[i].nxt) {
            int v=e[i].v; double w=e[i].w+mid;
            if (dis[u]+w<dis[v]) {
                dis[v]=dis[u]+w,cnt[v]=cnt[u]+1;
                if (cnt[v]>=n+2) return 1;
                if (!inq[v]) inq[v]=1,Q.push(v);
            }
        }
    }
    return 0;
}

inline int check(double mid) { //是否存在负环
    for (re int i=1;i<=n+2;++i) dis[i]=inq[i]=cnt[i]=0;
    for (re int i=1;i<=n+2;++i)
        if (spfa(i,mid)) return 1;
    return 0;
}

int main() {
    n=read(),m=read();
    for (re int i=1;i<=m;++i) {
        int u=read(),v=read(),a=read(),b=read(),c=read(),d=read();
        if (u==n+1) a=b=d=0;
        if (c) addEdge(v,u,a-d); addEdge(u,v,b+d);
    }
    double L=0,R=1e8;
    while (R-L>1e-4) {
        double mid=(L+R)/2;
        if (check(mid)) L=mid;
        else R=mid;
    }
    printf("%.2lf\n",L);
    return 0;
}
最后修改:2019 年 09 月 26 日 12 : 56 PM

发表评论