上下界网络流

我们知道,仅有上界的网络流,可以通过bfs寻找增广路进行扩流从而找到最大流。但如果给每条边的流量加一个下界,我们应该怎么办呢?

设边w的流量上界为sup,下界为inf

一个思路是先把每条边下界需要流的inf流量流满,然后再给出sup-inf的边,代表可流可不流,这样,就可以在保证取到流量下界的同时不超出流量上界。

那么我们具体该怎么实现呢?

无源汇上下界可行流:

无源汇:指的是网络中无源点及汇点,每个点都必须满足流入等于流出

假设现在需要连接一条边e(u,v),表示从u指向v,上界为sup,下界为inf,那么我们不直接连这条边,而是创建一个虚拟源点S和一个虚拟汇点T,连一条边(S,v),其流量为inf,再连一条边(u,T),其流量为inf, 然后再连接边(u,v),其流量为sup-inf。我们对所有的边都进行这样的拆分建图,则得到了下界的总和sum=\sum inf_i,然后我们再对虚拟源点S以及虚拟汇点T跑一次最大流,就可以强制使得每个v都满足最少inf的流量流入,同时,如果汇点能够有sum的流量流出,就说明有足够的通路使得强制流入的流量流出,也就是可以满足必须流满的下界和,即可行流存在。

题目链接:#115. 无源汇有上下界可行流 - 题目 - LibreOJ (loj.ac)

实现代码:

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int N=220,M=5E5+10;
#define LL long long
int h[N],nxt[M],tto[M],tot;
int dis[N],A[M],B[M],cur[N];
LL in[N],out[N],ww[M],low[M],up[M];
void add(int a,int b,int c){
    tto[++tot]=b;
    nxt[tot]=h[a];
    h[a]=tot;
    ww[tot]=c;
}
int n,m,S,T;

bool bfs(){
    memset(dis,0,sizeof(dis));
    queue<int> q;
    q.push(S);dis[S]=1;int u;
    while(!q.empty()){
        u=q.front();q.pop();
        for(int i=h[u],v;v=tto[i];i=nxt[i]){
            if(!dis[v]&&ww[i]){
                dis[v]=dis[u]+1;
                q.push(v);
                if(v==T) return true;
            }
        }
    }
    return false;
}

LL dfs(int u,LL mf){
    if(u==T) return mf;
    LL sum=0;
    for(int i=cur[u],v;v=tto[i];i=nxt[i]){
        cur[u]=i;
        if(dis[v]==dis[u]+1&&ww[i]){
        LL f=dfs(v,min(mf,ww[i]));
        ww[i]-=f;
        ww[i^1]+=f;
        sum+=f;
        mf-=f;
        if(!mf) break;
        }
    }
    if(!sum) dis[u]=0;
    return sum;
}

LL dinic(){
    LL flow=0;
    while(bfs()){
        memcpy(cur,h,sizeof(h));
        flow+=dfs(S,1ll<<33);
    }
    return flow;
}


int main(){
    scanf("%d%d",&n,&m);
    tot=1;S=n+1;T=n+2;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&A[i],&B[i],&low[i],&up[i]);
        out[A[i]]+=low[i];in[B[i]]+=low[i];
        add(A[i],B[i],up[i]-low[i]);
        add(B[i],A[i],0);
    }
    LL check=0;
    for(int i=1;i<=n;i++){
        if(in[i]){
            add(S,i,in[i]);add(i,S,0);
        }
        if(out[i]){
            add(i,T,out[i]);add(T,i,0);
        }
        check+=in[i];
    }
    if(dinic()!=check){
        cout<<"NO";return 0;
    }
    cout<<"YES"<<endl;
    for(int i=1;i<=m;i++){
        printf("%lld\n",up[i]-ww[i*2]);
    }
    return 0;
}

有源汇上下界最大流:

有源汇:指的是网络中有源点及汇点,则源点s和汇点t无需满足流入等于流出,因此在建图的时候增加一条t指向s的边,流量为无穷大INF,则可以虚拟满足他们也满足流入等于流出,从而将有源汇转变成了无源汇。

然后我们先对虚拟源点S和T跑一次最大流,若可行流存在,则此时残留网络中的必须边以及若干条非必须边已经跑完,且t指向s的边的流量刚好就为可行流的流量(刚好也是s指向t,也就是edge(t,s)的反向边的流量),因此我们再对s,t跑一次最大流,就可得到有源汇上下界的最大流。

PS:如果在跑第二次最大流之前把t指向s以及其反向边删掉了的话,就需要将第二次的最大流加上下界流量之和,才是有源汇上下界的最大流。

题目链接:#116. 有源汇有上下界最大流 - 题目 - LibreOJ (loj.ac)

实现代码:

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N=220,M=3E4+10;
#define LL long long
int h[N],nxt[M],tto[M],tot;
int cur[N],dis[N],a[M],b[M],In[N],out[N];
LL ww[M],sum;
int n,m,s,t,S,T;

void add(int a,int b,LL c){
    tto[++tot]=b;
    nxt[tot]=h[a];
    h[a]=tot;
    ww[tot]=c;
}

void read(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    tot=1;S=n+1;T=n+2;
    for(int i=1,lower,upper;i<=m;i++){
        scanf("%d%d%d%d",a+i,b+i,&lower,&upper);
        out[a[i]]+=lower;In[b[i]]+=lower;
        add(a[i],b[i],upper-lower);
        add(b[i],a[i],0);
    }
    for(int i=1;i<=n;i++){
        if(In[i]){
            add(S,i,In[i]);add(i,S,0);
        }
        if(out[i]){
            add(i,T,out[i]);add(T,i,0);
        }
        sum+=In[i];
    }
    add(t,s,1ll<<33);add(s,t,0);
}

bool bfs(int start,int des){
    memset(dis,0,sizeof(dis));
    queue<int> q;
    q.push(start);dis[start]=1;int u;
    while(!q.empty()){
        u=q.front();q.pop();
        for(int i=h[u],v;v=tto[i];i=nxt[i]){
            if(!dis[v]&&ww[i]){
                dis[v]=dis[u]+1;
                q.push(v);
                if(v==des) return true;
            }
        }
    }
    return false;
}

LL dfs(int u,LL mf,int des){
    if(u==des) return mf;
    LL sum=0;
    for(int i=cur[u],v;v=tto[i];i=nxt[i]){
        cur[u]=i;
        if(dis[v]==dis[u]+1&&ww[i]){
            LL f=dfs(v,min(mf,ww[i]),des);
            ww[i]-=f;
            ww[i^1]+=f;
            sum+=f;
            mf-=f;
            if(!mf) break;
        }
    }
    if(!sum) dis[u]=0;
    return sum;
}

LL dinic(int start,int des){
    LL flow=0;
    while(bfs(start,des)){
        memcpy(cur,h,sizeof(h));
        flow+=dfs(start,1ll<<33,des);
    }
    return flow;
}

int main(){
    read();
    if(dinic(S,T)!=sum){ printf("please go home to sleep");return 0; }
    printf("%lld",dinic(s,t));
    return 0;
}

有源汇上下界最小流:

由于下界存在,因此网络中一定存在一个满足所有流量下界的最小流量。我们还是按照上述方式建图,不过我们先不加上t指向s的这条边,先对S和T跑一次最大流,这样,我们就在源点无流量流出的情况下先流满了部分必须边及非必须边,(意思就是说有一些地方自己本身存在环流),然后再连上t指向s的这条边,再对S和T跑一次最大流,此时t指向s的边的流量就是最小流。

题目链接:#117. 有源汇有上下界最小流 - 题目 - LibreOJ (loj.ac)

实现代码:

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N = 5E4 + 10, M = 5E5 + 50;
#define LL long long
int h[N], nxt[M], tto[M], tot;
int cur[N], dis[N], a[M], b[M], In[N], out[N];
LL ww[M], sum;
int n, m, s, t, S, T;

void add(int a, int b, LL c) {
    tto[++tot] = b;
    nxt[tot] = h[a];
    h[a] = tot;
    ww[tot] = c;
}

void read() {
    scanf("%d%d%d%d", &n, &m, &s, &t);
    tot = 1;
    S = n + 1;
    T = n + 2;
    LL lower, upper;

    for (int i = 1; i <= m; i++) {
        scanf("%d%d%lld%lld", a + i, b + i, &lower, &upper);
        out[a[i]] += lower;
        In[b[i]] += lower;
        add(a[i], b[i], upper - lower);
        add(b[i], a[i], 0);
    }

    for (int i = 1; i <= n; i++) {
        if (In[i]) {
            add(S, i, In[i]);
            add(i, S, 0);
        }

        if (out[i]) {
            add(i, T, out[i]);
            add(T, i, 0);
        }

        sum += In[i];
    }
}

bool bfs(int start, int des) {
    memset(dis, 0, sizeof(dis));
    queue<int> q;
    q.push(start);
    dis[start] = 1;
    int u;

    while (!q.empty()) {
        u = q.front();
        q.pop();

        for (int i = h[u], v; v = tto[i]; i = nxt[i]) {
            if (!dis[v] && ww[i]) {
                dis[v] = dis[u] + 1;
                q.push(v);

                if (v == des)
                    return true;
            }
        }
    }

    return false;
}

LL dfs(int u, LL mf, int des) {
    if (u == des)
        return mf;

    LL sum = 0;

    for (int i = cur[u], v; v = tto[i]; i = nxt[i]) {
        cur[u] = i;

        if (dis[v] == dis[u] + 1 && ww[i]) {
            LL f = dfs(v, min(mf, ww[i]), des);
            ww[i] -= f;
            ww[i ^ 1] += f;
            sum += f;
            mf -= f;

            if (!mf)
                break;
        }
    }

    if (!sum)
        dis[u] = 0;

    return sum;
}

LL dinic(int start, int des) {
    LL flow = 0;

    while (bfs(start, des)) {
        memcpy(cur, h, sizeof(h));
        flow += dfs(start, 1ll << 33, des);
    }

    return flow;
}

int main() {
    read();
    sum -= dinic(S, T);
    add(t, s, 1ll << 33);
    add(s, t, 0);
    sum -= dinic(S, T);

    if (sum) {
        printf("please go home to sleep");
        return 0;
    }

    printf("%lld", ww[tot]);
    return 0;
}

有源汇上下界最小费最大流:

由于下界必须流满,我们先算出流满下界容量所需的花费cost_1,然后再对残留网正常对有源汇上下界最大流跑一次最小费最大流算法cost_2,则答案就是cost_1+cost_2

题目链接:P4043 [AHOI2014/JSOI2014] 支线剧情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

实现代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#define LL long long
using namespace std;
const int N = 350, M = 5E5 + 10;
int h[N], nxt[M], tto[M], tot;
LL ww[M], cst[M], mf[N], cost, sum, IN[N], OUT[N];
int dis[N], vis[N], pre[N];
int n, s, t, S, T;
void add(int a, int b, LL c, LL d) {
    tto[++tot] = b;
    nxt[tot] = h[a];
    h[a] = tot;
    ww[tot] = c;
    cst[tot] = d;
}

bool spfa() {
    memset(dis, 0x3f, sizeof(dis));
    memset(mf, 0, sizeof(mf));
    queue<int> q;
    q.push(S); vis[S] = true; dis[S] = 0; int u;  mf[S] = 1ll << 33;
    while (!q.empty()) {
        u = q.front(); q.pop(); vis[u] = false;
        for (int i = h[u], v; v = tto[i]; i = nxt[i]) {
            if (dis[v] > dis[u] + cst[i] && ww[i]) {
                dis[v] = dis[u] + cst[i];
                mf[v] = min(mf[u], ww[i]);
                pre[v] = i;
                if (!vis[v]) {
                    vis[v] = true; q.push(v);
                }
            }
        }
    }
    return mf[T];
}

LL ek() {
    LL flow = 0;
    cost = 0;
    while (spfa()) {
        int v = T, i;
        while (v != S) {
            i = pre[v];
            ww[i] -= mf[T];
            ww[i ^ 1] += mf[T];
            v = tto[i ^ 1];
        }
        flow += mf[T];
        cost += mf[T] * dis[T];
    }
    return flow;
}

int main() {
    scanf("%d", &n);
    tot = 1; s = 1; t = n + 1;
    LL limit = 1ll << 33;
    T = n + 2, S = n + 3;
    for (int i = 1, k; i <= n; i++) {
        scanf("%d", &k);
        for (int j = 1, b, ct; j <= k; j++) {
            scanf("%d%d", &b, &ct);
            add(i, b, limit, ct);
            add(b, i, 0, -ct);
            IN[b]++; OUT[i]++;
            sum+=ct;
        }
    }
    for (int i = 2; i <= n; i++) {
        add(i, t, limit, 0);
        add(t, i, 0, 0);
    }
    for (int i = 1; i <= n; i++) {
        if (IN[i]) {
            add(S, i, IN[i], 0); add(i, S, 0, 0);
        }
        if (OUT[i]) {
            add(i, T, OUT[i], 0); add(T, i, 0, 0);
        }
    }
    add(t, s, limit, 0); add(s, t, 0, 0);
    ek();
    LL cost1 = cost;
    printf("%lld",cost1+sum);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值