有源汇上下界最大流
只记录一下做法,原理不证明。。
有源汇上下界最大流,指的是
有 n n n个点, m m m条边,每条边 e e e有一个流量下界 l o w e r lower lower和流量上界 u p p e r upper upper,给定源点 s s s和汇t点,求源点到汇点的最大流
(相当于在之前的最大流的基础上,每条边加上一个流量下界,来求最大流)
做这个题之前先来看这样一条道题
无源汇有上下界可行流
有 n n n个点, m m m条边,每条边 e e e有一个流量下届 l o w e r lower lower和流量上界 u p p e r upper upper,求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
输入
第一行两个正整数 n 、 m n、m n、m
之后的 m m m行,每行四个整数 s , t , l o w e r , u p p e r s,t,lower,upper s,t,lower,upper
输出
如果无解输出一行 N O NO NO,否则第一行输出 Y E S YES YES,之后 m m m行每行一个整数,表示每条边的流量
样例输入
4 6
1 2 1 3
2 3 1 3
3 4 1 3
4 1 1 3
1 3 1 3
4 2 1 3
样例输出
YES
1
2
3
2
1
1
由于每一条弧的下界是必须要满足的流量限制,因此可以把这条弧分出一条必要弧出来,权值就是下界的流量大小
如图

将下界作为必要弧分离出来,原来的弧的权值为 u p − l o w up-low up−low

接着建立一个源点 s s s,一个汇点 t t t,对于一个必要弧 ( u , v ) (u,v) (u,v)使用源点和汇点串成 ( u , t ) (u,t) (u,t)和 ( s , v ) (s,v) (s,v),这样可以建立一个等价网络

接着就是最大流算法,求从 s s s到 t t t求最大流即可,但是需要注意的是
- 必要弧必须流满,也就是如果求出来的最大流 m a x f l o w < ∑ i = 1 m l o w e r ( i ) maxflow < \sum_{i=1}^{m}lower(i) maxflow<∑i=1mlower(i),则不存在解。
- 求出最大流后,用原来的网络减去算法跑完最大流后的网络,就是要求的可行流
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define N 205
#define M 10205
#define INF 0x7fffffff
using namespace std;
inline int min(int a, int b) {
if(a > b) return b;
else return a;
}
struct Edge {
int to, next, cap, up; //其中的up用来标记初始网络的边的值
Edge () {
to = next = cap = up = 0;
}
}edge[M * 10];
int head[N], cur[N], dep[N];
int n, m, s, t, cnt = -1, maxflow;
queue <int> q;
void addEdge (int u, int v, int cost) {
edge[++cnt].to = v;
edge[cnt].cap = cost;
edge[cnt].next = head[u];
head[u] = cnt;
}
bool bfs () {
while(!q.empty()) q.pop();
for(int i = 0; i <= n+1; i++) cur[i] = head[i], dep[i] = -1;
dep[s] = 0;
q.push(s);
while(!q.empty()) {
int x = q.front(); q.pop();
for(int i = head[x]; i != -1; i = edge[i].next) {
int v = edge[i].to, cap = edge[i].cap;
if(dep[v] == -1 && cap > 0) {
dep[v] = dep[x] + 1;
q.push(v);
}
}
}
if(dep[t] == -1) return 0;
else return 1;
}
int dfs (int x, int mx) {
int flow;
if(x == t) return mx;
for(int i = cur[x]; i != -1; i = edge[i].next) {
cur[x] = i;
int v = edge[i].to, cap = edge[i].cap;
if(cap > 0 && dep[v] == dep[x] + 1 && (flow = dfs(v, min(mx, cap))) ) {
edge[i].cap -= flow;
edge[i^1].cap += flow;
return flow;
}
}
return 0;
}
void Dinic () {
int res = 0;
while(bfs())
while(res = dfs(s, INF))
maxflow += res;
}
int main () {
memset(head, -1, sizeof(head));
int u, v, low, up, sum = 0;
scanf("%d %d", &n, &m);
s = 0, t = n + 1;
for(int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &u, &v, &low, &up);
addEdge(u, v, up-low); //分离必要弧
edge[cnt].up = up;
addEdge(v, u, 0); //反向建边
addEdge(u, t, low), addEdge(t, u, 0); //连接必要弧和s以及t的边
addEdge(s, v, low), addEdge(v, s, 0);
sum += low; //记录必要弧的权值之和
}
Dinic();
if(maxflow == sum) {
printf("YES\n");
for(int i = 0; i <= cnt; i++)
if(edge[i].up)
printf("%d\n", edge[i].up - edge[i].cap); //求可行流
}
else
printf("NO\n");
return 0;
}
有源汇的上下界最大流。
需要首先将有源汇的上下界最大流转换为无源汇的一个循环流,按照求解可行流的方法,建立超级源点汇点,然后使用最大流算法计算。
假设网络如下

建立一个无源汇的循环流很简单,只需从连一条 t    − > s t\,\,->s t−>s的权值为 ∞ + \infty^+ ∞+的弧即可

然后把之前的 s s s和 t t t当做普通的点处理,建立超级源点和超级汇点 s t st st,和 e d ed ed。

然后使用最大流算法,求从 s t st st到 e d ed ed的流,并判断是否是一个可行流,如果是再用最大流算法跑一遍从 s s s到 t t t即可
- 注意要建立很多条边,每一条边都要反向建立边
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define N 205
#define M 10005
#define INF 0x7fffffff
#define min(a, b) a>b?b:a
using namespace std;
struct Edge {
int to, next, cap;
}edge[M * 10];
int n, m, s, t, maxflow, cnt = -1;
int head[N], dep[N], cur[N];
queue <int> q;
inline void addEdge (int u, int v, int cost) {
edge[++cnt].cap = cost;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
bool bfs (int ss, int tt) {
while(!q.empty()) q.pop();
for(int i = 0; i <= n + 1; i++) cur[i] = head[i], dep[i] = -1;
dep[ss] = 0;
q.push(ss);
while(!q.empty()) {
int x = q.front(); q.pop();
for(int i = head[x]; i != -1; i = edge[i].next) {
int v = edge[i].to, cap = edge[i].cap;
if(cap > 0 && dep[v] == -1) {
dep[v] = dep[x] + 1;
q.push(v);
}
}
}
if(dep[tt] == -1) return 0;
else return 1;
}
int dfs (int x, int tt, int mx) {
int flow;
if(x == tt) return mx;
for(int i = cur[x]; i != -1; i = edge[i].next) {
cur[x] = i;
int v = edge[i].to, cap = edge[i].cap;
if(cap > 0 && dep[v] == dep[x] + 1 && (flow = dfs(v, tt, min(cap, mx))) ) {
edge[i].cap -= flow;
edge[i^1].cap += flow;
return flow;
}
}
return 0;
}
void Dinic(int ss, int tt) {
int res = 0;
while( bfs(ss, tt) )
while( res = dfs(ss, tt, INF) )
maxflow += res;
}
int main () {
int u, v, cost, st, ed, low, up, sum = 0;
scanf("%d %d %d %d", &n, &m, &s, &t);
st = 0, ed = n + 1;
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &u, &v, &low, &up);
addEdge(u, v, up - low), addEdge(v, u, 0);
addEdge(u, ed, low), addEdge(ed, u, 0);
addEdge(st, v, low), addEdge(v, st, 0);
sum += low;
}
addEdge(t, s, INF);
addEdge(s, t, 0);
Dinic(st, ed); //先判断可行流
if(sum > maxflow) {
printf("please go home to sleep\n");
return 0;
}
maxflow = 0;
Dinic(s, t); //再判断最大流
printf("%d\n", maxflow);
return 0;
}