有上下界的网络流具体可以分为以下三种:
1.无源汇有上下界可行流
例题:ZOJ - 2314
题意:有 n n n 个节点,且有 m m m 条水管,每条水管的流向为单向,且每条水管的流量有限制,第 i i i 条水管的流量 f i ( l i ≤ f i ≤ c i ) f_i(l_i \leq f_i \leq c_i) fi(li≤fi≤ci) ,问是否存在一种流量分配,使得这m条水管的流量满足条件,如果存在,则输出每条水管的流量。
思路:一般的网络流算法,对于流量是只有上界的,下界默认为0,那么具有非0下界,我们需要构造一个新的图使得其下界为0。
建图方法:对于第
i
i
i 条边,上下界为
c
i
,
l
i
c_i,l_i
ci,li ,则新的边上下界为
c
i
−
l
i
c_i-l_i
ci−li ,0。
设置一个数组
d
u
[
i
]
du[i]
du[i] 来记录每个节点的流量。
d
u
[
i
]
=
i
n
[
i
]
−
o
u
t
[
i
]
du[i] = in[i]-out[i]
du[i]=in[i]−out[i]
(
i
n
[
i
]
in[i]
in[i]:节点
i
i
i 的所有入流的
l
[
i
]
l[i]
l[i] 的和,
o
u
t
[
i
]
out[i]
out[i]:节点
i
i
i 的所有出流的
l
[
i
]
l[i]
l[i] 的和)
同时再增加一个超级源点
s
t
st
st ,以及一个超级汇点
e
n
en
en 。
对于除了超级超级源点和汇点外的其他点:
若
d
u
[
i
]
>
0
du[i] > 0
du[i]>0 ,
s
t
st
st 到
i
i
i 连一条上界为
d
u
[
i
]
du[i]
du[i] 的边。
若
d
u
[
i
]
<
0
du[i] < 0
du[i]<0 ,
i
i
i 到
e
n
en
en 连一条上界为
−
d
u
[
i
]
-du[i]
−du[i] 的边。
到此整个新图就建好了,在新图上跑最大流即可,若满流则说明有解。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 205
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
x=0;
char c;T t=1;
while(((c=getchar())<'0'||c>'9')&&c!='-');
if(c=='-'){t=-1;c=getchar();}
do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
x*=t;
}
template <typename T>
void write(T x)
{
int len=0;char c[21];
if(x<0)putchar('-'),x*=(-1);
do{++len;c[len]=(x%10)+'0';}while(x/=10);
_ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;
struct edge
{
int to, next, w, f;
} e[NUM * NUM * 2];
int du[NUM], head[NUM], num[NUM], depth[NUM];
int low[NUM * NUM * 2], sum_du;
inline void add_edge(const int &x, const int &y, const int &c)
{
e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs()
{
int x, y;
mst(num, 0, int, n + 2);
mst(depth, INF, int, n + 2);
queue<int> q;
q.push(n + 1), ++num[depth[n + 1] = 1];
while (!q.empty())
{
x = q.front(), q.pop();
for (int i = head[x]; i != -1; i = e[i].next)
{
y = e[i].to;
if (depth[y] != INF || e[i].w != 0)
continue;
++num[depth[y] = depth[x] + 1];
q.push(y);
}
}
}
int dfs(const int &vertex, int minx)
{
if (minx == 0 || vertex == n + 1)
return minx;
int f, x, flow = 0;
for (int i = head[vertex]; i != -1; i = e[i].next)
{
x = e[i].to;
if (depth[vertex] - 1 == depth[x] && (f = dfs(x, min(minx, e[i].w))) != 0)
{
minx -= f, flow += f;
e[i].w -= f, e[i].f += f;
e[i ^ 1].w += f, e[i ^ 1].f -= f;
if (minx == 0)
return flow;
}
}
if (!(--num[depth[vertex]]))
depth[0] = n + 3;
++num[++depth[vertex]];
return flow;
}
inline bool ISAP()
{
int maxflow = 0;
bfs();
while (depth[0] <= n + 2)
maxflow += dfs(0, INF);
return maxflow == sum_du;
}
void AC()
{
int l, c, x, y;
int T;
read(T);
while (T--)
{
read(n), read(m);
mst(head, -1, int, n + 2), mst(du, 0, int, n + 2), edge_num = -1, sum_du = 0;
ffor(i, 1, m)
{
read(x), read(y), read(l), read(c);
add_edge(x, y, c - l);
add_edge(y, x, 0);//反向边,无向图的上界和正向边相同,有向图的为0
du[y] += l;
du[x] -= l;
low[i] = l;
}
ffor(i, 1, n)//加附加边
{
if (du[i] < 0)
add_edge(i, n + 1, -du[i]), add_edge(n + 1, i, 0);
else if (du[i] > 0)
{
add_edge(0, i, du[i]), add_edge(i, 0, 0);
sum_du += du[i];
}
}
if (ISAP())
{
puts("YES");
ffor(i, 1, m) write(low[i] + e[(i - 1) << 1].f), putchar('\n');
}
else
puts("NO");
}
}
int main()
{
AC();
return 0;
}
2.有源汇有上下界最大流
例题:ZOJ - 3229
题意:给 m m m 个女生拍照,每个女生至少要拍 G i G_i Gi 张照片,一共有 n n n 天的时间,第 i i i 天有 c i c_i ci 位女生可以拍照,且当天一共只能拍 d i d_i di 张照片,而第 c i , j c_{i,j} ci,j 位女生当天至少要拍 l j l_j lj 张,最多只能拍 r j r_j rj 张,问如何分配能够满足这 m m m 个女生的拍照要求,若可以,输出能够拍的照片数最多的拍照方案。
思路:新增一个源点
s
s
s ,一个汇点
t
t
t ,源点到第
i
i
i 天连一条容量为
d
i
d_i
di 的边,第
i
i
i 个女生到汇点连一条下界为
G
i
G_i
Gi ,上界为无穷大的边。可以建造新图转化为无源汇有上下界的问题求解。
建图方法:由汇点到源点连一条上界为无穷大,下界为零的边,这样整个图就是个无源汇有上下界的问题,之后设一个超级源点
s
t
st
st ,超级汇点
e
n
en
en,再按无源汇有上下界的求解方法建图,即可判断是否有可行解。
判断完是否有可行解之后,由于残余网络中除了超级源点与汇点外的边,仍可能存在增广路,所以需要排除超级源点与汇点后再求解一次最大流。最后所得即为最大解。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 1380
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
x=0;
char c;T t=1;
while(((c=getchar())<'0'||c>'9')&&c!='-');
if(c=='-'){t=-1;c=getchar();}
do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
x*=t;
}
template <typename T>
void write(T x)
{
int len=0;char c[21];
if(x<0)putchar('-'),x*=(-1);
do{++len;c[len]=(x%10)+'0';}while(x/=10);
_ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;//天数标号2~n+1,女生标号n+2~n+m+1
struct edge
{
int to, next, w, f;
} e[NUM * 60];
int du[NUM], head[NUM], num[NUM], depth[NUM];
int low[NUM * 60], sum_du;
int Index[370][105];
inline void add_edge(const int &x, const int &y, const int &c)
{
e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs(const int &s, const int &t)
{
int x, y;
mst(num, 0, int, t - s + 2), mst(depth, INF, int, t - s + 2);
queue<int> q;
q.push(t), ++num[depth[t] = 1];
while (!q.empty())
{
x = q.front(), q.pop();
for (int i = head[x]; i != -1; i = e[i].next)
{
y = e[i].to;
if (depth[y] != INF || e[i].w < e[i].f)
continue;
++num[depth[y] = depth[x] + 1];
q.push(y);
}
}
}
int dfs(const int &s, const int &t, const int &vertex, int minx)
{
if (minx == 0 || vertex == t)
return minx;
int f, x, flow = 0;
for (int i = head[vertex]; i != -1; i = e[i].next)
{
x = e[i].to;
if (depth[vertex] - 1 == depth[x] && (f = dfs(s, t, x, min(minx, e[i].w))) != 0)
{
minx -= f, flow += f;
e[i].w -= f, e[i].f += f;
e[i ^ 1].w += f, e[i ^ 1].f -= f;
if (minx == 0)
return flow;
}
}
if (!(--num[depth[vertex]]))
depth[s] = t - s + 2;
++num[++depth[vertex]];
return flow;
}
inline int ISAP(const int &s, const int &t)
{
int maxflow = 0;
bfs(s, t);
while (depth[s] <= t - s + 1)
maxflow += dfs(s, t, s, INF);
return maxflow;
}
void AC()
{
int d, x, y, l, r;
int flow;
while (scanf("%d", &n) != EOF)
{
read(m);
mst(head, -1, int, n + m + 4), mst(du, 0, int, n + m + 4), edge_num = -1, sum_du = 0;
ffor(i, 1, m)
{
read(x);
add_edge(1 + n + i, n + m + 2, INF - x), add_edge(n + m + 2, 1 + n + i, 0);
du[n + m + 2] += x;
du[1 + n + i] -= x;
}
ffor(i, 1, n)
{
read(Index[i][0]), read(d);
x = 1 + i;
add_edge(1, x, d), add_edge(x, 1, 0);
ffor(j, 1, Index[i][0])
{
read(y), read(l), read(r);
y += (2 + n);
add_edge(x, y, r - l), low[Index[i][j] = edge_num] = l, add_edge(y, x, 0);
du[y] += l;
du[x] -= l;
}
}
add_edge(n + m + 2, 1, INF), add_edge(1, n + m + 2, 0);
ffor(i, 1, n + m + 2)
{
if (du[i] < 0)
add_edge(i, n + m + 3, -du[i]), add_edge(n + m + 3, i, 0);
else if (du[i] > 0)
{
add_edge(0, i, du[i]), add_edge(i, 0, 0);
sum_du += du[i];
}
}
flow = ISAP(0, n + m + 3);
if (flow == sum_du)
{
head[0] = head[n + m + 3] = -1;
flow = ISAP(1, n + m + 2);
write(flow), putchar('\n');
ffor(i, 1, n) ffor(j, 1, Index[i][0]) write(low[Index[i][j]] + e[Index[i][j]].f), putchar('\n');
}
else
puts("-1");
putchar('\n');
}
}
int main()
{
AC();
return 0;
}
3.有源汇有上下界最小流
例题:Codeforces - ACMSGURU - 176
题意:一共有 n n n 个节点, m m m 条管道,每条管道为单向流通,整个图的起点为 1 1 1 ,终点为 n n n 。对于第 i i i 个管道,有一个 z i z_i zi 表示该管道的容量,且有一个 c i c_i ci ,若 c i = 1 c_i=1 ci=1 ,则该管道的流量下界 = = = 上界 c i c_i ci ,若 c i = 0 c_i=0 ci=0 ,则该管道下界为 0 0 0 。现在从节点 1 1 1 输入一定流量,问是否可以使整个图保证流通,若可以,则最小得往节点 1 1 1 输入多少流量。
思路:按照无源汇有上下界最大流建图,但是不要在初始源点和汇点之间连容量为无穷大的那条边。然后跑一遍最大流,然后就从初始汇点往源点连一条容量为无穷大的边,两次最大流的和就是答案,之后依旧按照无源汇的判断是否有可行流,以及求每条边流量。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 105
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
x=0;
char c;T t=1;
while(((c=getchar())<'0'||c>'9')&&c!='-');
if(c=='-'){t=-1;c=getchar();}
do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
x*=t;
}
template <typename T>
void write(T x)
{
int len=0;char c[21];
if(x<0)putchar('-'),x*=(-1);
do{++len;c[len]=(x%10)+'0';}while(x/=10);
_ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;//天数标号2~n+1,女生标号n+2~n+m+1
struct edge
{
int to, next, w, f;
} e[NUM * NUM * 2];
int du[NUM] = {}, head[NUM], num[NUM] = {}, depth[NUM];
int low[NUM * NUM * 2], sum_du;
inline void add_edge(const int &x, const int &y, const int &c)
{
e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs(const int &s, const int &t)
{
int x, y;
mst(depth, INF, int, s + t + 1);
queue<int> q;
q.push(t), ++num[depth[t] = 1];
while (!q.empty())
{
x = q.front(), q.pop();
for (int i = head[x]; i != -1; i = e[i].next)
{
y = e[i].to;
if (depth[y] != INF || e[i].w < e[i].f)
continue;
++num[depth[y] = depth[x] + 1];
q.push(y);
}
}
}
int dfs(const int &s, const int &t, const int &vertex, int minx)
{
if (minx == 0 || vertex == t)
return minx;
int f, x, flow = 0;
for (int i = head[vertex]; i != -1; i = e[i].next)
{
x = e[i].to;
if (depth[vertex] - 1 == depth[x] && (f = dfs(s, t, x, min(minx, e[i].w))) != 0)
{
minx -= f, flow += f;
e[i].w -= f, e[i].f += f;
e[i ^ 1].w += f, e[i ^ 1].f -= f;
if (minx == 0)
return flow;
}
}
if (!(--num[depth[vertex]]))
depth[s] = t - s + 2;
++num[++depth[vertex]];
return flow;
}
inline int ISAP(const int &s, const int &t)
{
int maxflow = 0;
bfs(s, t);
while (depth[s] <= t - s + 1)
maxflow += dfs(s, t, s, INF);
return maxflow;
}
void AC()
{
int x, y, z, c;
int flow;
read(n), read(m);
mst(head, -1, int, n + 2), edge_num = -1, sum_du = 0;
ffor(i, 1, m)
{
read(x), read(y), read(z), read(c);
c = ((c == 1) ? z : 0);
add_edge(x, y, z - c), add_edge(y, x, 0);
low[i] = c;
du[y] += c;
du[x] -= c;
}
ffor(i, 1, n)
{
if (du[i] < 0)
add_edge(i, n + 1, -du[i]), add_edge(n + 1, i, 0);
else if (du[i] > 0)
{
add_edge(0, i, du[i]), add_edge(i, 0, 0);
sum_du += du[i];
}
}
flow = ISAP(0, n + 1);
add_edge(n, 1, INF), add_edge(1, n, 0);
flow += ISAP(0, n + 1);
if (flow == sum_du)
{
write(e[edge_num ^ 1].f), putchar('\n');
ffor(i, 1, m) write(low[i] + e[(i - 1) << 1].f), putchar(' ');
putchar('\n');
}
else
puts("Impossible");
}
int main()
{
AC();
return 0;
}