上下界网络流

上下界网络流,就是每条边既有流量上界也有流量下界的网络流。分为无源汇上下界可行流、有源汇上下界最大流、有源汇上下界最小流三种。

我们回顾网络流模型的两个关键要素:流量守恒和容量限制。流量守恒是除了源点和汇点之外点的总流量均为 \(0\) 的限制,而容量限制则是对每条边加了一个限制:不仅要小于一个 \(c_上\),还要大于一个 \(c_下\)

想象之前学的最大流模型都是有源点汇点,并且求的最大流是源点流出的流量最大值。没有源点和汇点的时候,每个点流量都为 \(0\)。没有下界的时候图可以静如死水,也就没有什么最大流一说了。加了下界之后,网络流这张图不可以静如死水,可能必须要动起来了。这时候我们就遇到了一个问题,需要求出一个可行流。

例如这样,用 \([c_{下},c_上]\) 表示上下界,红色标注是一个可行流。
image

加上源点汇点之后,我们同样有求最大流环节;并且由于加上了上下界,我们还有求最小流环节。

image

例如这张图,最大流和最小流分别为如下两张图:

image
image

接下来我们具体讨论这三种问题怎么解决。

无源汇上下界网络流

上下界网络流是没法维护的,我们基本的想法是要把它转化为只有上界。考虑对于一条边,我们把流量和容量都减去 \(c_下\),然后最后还原的时候复原。

这样做会有一个什么错误呢?容量限制依然满足,但是流量不守恒了!这是因为,每个点删掉的出边和入边下界和之差不一定是 \(0\)。假设所有入边下界之和为 \(c_入\),出边下界之和为 \(c_出\)。原流量为 \(0\) 的点如今流量(流出-流入)为 \(c_入 - c_出\)

这怎么办?我们其实可以考虑建立一个虚拟的源点,补充消失的流量。也就是说,要求 \(s\) 对该点有 \(c_入 - c_出\)流量。如果是负数,要求该点对 \(t\)\(c_出 - c_入\)流量

怎么处理新加流量?其实直接加为容量求最大流就好了,并且要求这个最大流必须满流。也就是说,\(s\) 连出去的每一条边都必须满流,\(t\) 连进来的每一条边都必须满流。这两个一定同时达到,因为 \(\sum \limits_{i} c_入 - c_出 = 0\)

考虑该做法的正确性:对于新图 \(G'\)(唯一确定)和新图的一个最大流 \(f'\),对最大流只取出中间部分,每条边加上原有的下界,依然保持容量限制和流量守恒。

另外,原图的每一个可行流和新图的每一个最大流一一对应。这个不证明了。

有源汇上下界最大最小流

考虑存在源点和汇点的情况。这种情况下,我们先建一条 \(t \rightarrow s\),容量为 \(+\inf\)。然后变成无源汇上下界可行流,先求出一个可行流再说。然后直接在这个可行流上抓出原图,在残量网络上直接跑 \(s \rightarrow t\) 或者 \(t \rightarrow s\)最大流(取决于题目问的是最大还是最小流)。

这个为什么是对的?考虑增广路定理:对于原图 \(G\) 的一个流 \(f\),如果原图上不存在增广路,那么 \(f\) 是最大流。

那么这张图里呢?求出的流 \(f\) 是新图里的,甚至不是原图的可行流。(\(S,T\) 流量不守恒)但是我们把新图里的任意一个\(f\) 上寻找若干条 \(s \rightarrow t\) 的增广路得到的图中 \(t \rightarrow s\) 的那条新增边上的流量 \(f'\) 减去 \(f\),得到的是一个 \(s \rightarrow t\) 的可行流。为什么呢?

证明:首先 \(S,T\) 满流,因此减掉之后 \(S,T\) 流量不仅守恒,而且不存在流量。这样图的范围就缩减为原图的了。接着看每条边的流量。显然是增广路上的流量,所以是可行的。然后 \(s,t\) 两个点如果看成之前的无源汇,那么 \(t \rightarrow s\) 减去增广路上的对应流量即可,还是一个无源汇可行流。如果看成原图上的有源汇,那么是正常的退流/加流操作。

由于 \(|f|\) 固定,所以只需要找到一个最大的新增边上的流量 \(|f'|\) 即可。(关键边思想)

这里来一张求最小流的图,看的比较清楚。

原图:
image
新图:
image
找到的一个满流:
image
dinic的结果:
image
最小流:
image

【模板】
无源汇上下界可行流:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
//调不出来给我对拍!
int head[220], nxt[(220 + 10220) * 2], to[(220 + 10220) * 2], cap[(220 + 10220) * 2];  //cap = c - f
int sum[220];  //c_入-c_出 
int cd[(220 + 10220) * 2];
int cur[220];
int dep[220];
int s = 0, t = 201, cnt = -1;
void add(int a,int b,int c,int d) {
    nxt[++cnt] = head[a]; to[cnt] = b; cap[cnt] = d - c; sum[a] -= c; cd[cnt] = c; head[a] = cnt;
    nxt[++cnt] = head[b]; to[cnt] = a; cap[cnt] = 0; sum[b] += c; cd[cnt] = 0; head[b] = cnt;    
}
bool bfs() {
    memset(dep, -1, sizeof(dep));
    queue<int> q; dep[s] = 0; cur[s] = head[s];
    q.push(s);
    while(!q.empty()) {
        int now = q.front(); q.pop();
        for(int i = head[now]; ~i; i = nxt[i]) {
            if(cap[i] > 0 && (dep[to[i]] == -1)) {
                dep[to[i]] = dep[now] + 1; cur[to[i]] = head[to[i]];
                q.push(to[i]);
                if(to[i] == t) return 1;
            }
        }
    }
    return 0;
}
int dfs(int now, int limit) {
    if(now == t) return limit;
    int flow = 0;
    for(int i = cur[now]; ~i && flow < limit; i = nxt[i]) {
        cur[now] = i;
        if(dep[to[i]] == dep[now] + 1 && cap[i] > 0) {
            int tmp = dfs(to[i], min(cap[i], limit - flow));
            if(!tmp) {dep[to[i]] = -1;}
            flow += tmp; cap[i] -= tmp; cap[i ^ 1] += tmp;
        }
    }
    return flow;
}
int dinic() {
    int maxflow = 0, flow = 0;
    while(bfs()) {
        flow = dfs(s, inf); maxflow += flow;
    }
    return maxflow;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int n,m;cin>>n>>m;
    t = n + 1;
    memset(head, -1, sizeof(head));
    f(i,1,m){
        int a,b,c,d;cin>>a>>b>>c>>d;
        add(a,b,c,d);
    }
    int full = 0;
    vector<int> tick;
    f(i,1,n){
        if(sum[i]>0){add(s, i, 0, sum[i]); tick.push_back(cnt); full += sum[i];}
        else if(sum[i] < 0) {add(i, t, 0, -sum[i]);}
    }
    dinic();
    int lluf = 0;
    for(int i : tick) lluf += cap[i];
    if(lluf < full) {cout << "NO\n"; return 0;}
    else {cout<< "YES\n";}
    for(int i = 1; i <= 2 * m - 1; i += 2) {
        cout << cd[i ^ 1] + cap[i] << endl;
    }
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

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

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
//调不出来给我对拍!
int S, T;
int head[220], to[(10010 + 220) * 2], nxt[(10010 + 220) * 2], cap[(10010 + 220) * 2];
int cur[220], sum[220], dep[220];
int cnt = -1;
int cd[(10010 + 220) * 2];
void add(int a, int b, int c, int d) {
    nxt[++cnt] = head[a]; to[cnt] = b; cap[cnt] = d - c; head[a] = cnt; cd[cnt] = c;
    nxt[++cnt] = head[b]; to[cnt] = a; cap[cnt] = 0; head[b] = cnt; cd[cnt] = 0;
    sum[a] -= c; sum[b] += c;
}
bool bfs() {
    memset(dep, -1, sizeof(dep));
    queue<int> q; q.push(S); cur[S] = head[S]; dep[S] = 0;
    while(!q.empty()) {
        int now = q.front(); q.pop();
        for(int i = head[now]; ~i; i = nxt[i]) {
            if(cap[i] && (dep[to[i]] == -1)) {
                dep[to[i]] = dep[now] + 1; cur[to[i]] = head[to[i]];
                q.push(to[i]); 
                if(to[i] == T) return 1;
            }
        }
    }
    return 0;
}
int dfs(int now, int limit) {
    if(now == T) return limit;
    int flow = 0;
    for(int i = cur[now]; ~i && flow < limit; i = nxt[i]) {
        cur[now] = i;
        if(dep[to[i]] == dep[now] + 1 && cap[i] > 0) {
            int tmp = dfs(to[i], min(cap[i], limit - flow));
            if(tmp == 0) {dep[to[i]] = -1;}
            cap[i] -= tmp; cap[i ^ 1] += tmp; flow += tmp;
        }
    }
    return flow;
}
int dinic() {
    int maxflow = 0, flow;
    while(bfs()) {flow = dfs(S, inf); maxflow += flow;}
    return maxflow;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int n,m,s,t; cin >> n >> m >> s >> t;
    memset(head, -1, sizeof(head));
    S = 0, T = n + 1;
    f(i, 1, m) {
        int a, b,c,d;cin>>a>>b>>c>>d;
        add(a,b,c,d);
    }
    vector<int> tick;
    int full = 0;
    f(i, 1, n) {
        if(sum[i] > 0) {full += sum[i]; tick.push_back(cnt); add(S, i, 0, sum[i]);}
        else if(sum[i] < 0) {add(i, T, 0, -sum[i]);}
    }
    add(t, s, 0, inf); int adb = cnt;
    int lluf = dinic();
    int ans = cap[adb];
  //  cout << ans << endl;
  //  cout << full << endl;
   // int lluf = 0;
  //  for(int i : tick) {
  //      lluf += cap[i]; 
  //  }
  //  cout << lluf << endl;
    if(lluf < full) {cout << "No Solution" << endl; return 0;}
    else {
        S = s; T = t;
        cap[adb] = cap[adb ^ 1] = 0;
        ans += dinic();
    }
    cout << ans << endl;
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}
posted @ 2023-01-09 17:46  OIer某罗  阅读(245)  评论(0编辑  收藏  举报