有上下界的网络最大流(无源点无汇点)
这个时候,不止有流量的上界,还有了流量的下界。所以这个东西理所应当难一些些。
我们采用这样的一种方式:先找出可行流,然后再考虑找出这些可行流里面权值最大的。
找到可行流(无源点无汇点)
我们思考怎么找到可行流。
先考虑一个简单的问题:
- 有 555 的苹果,333 个小朋友,每一个小朋友分到 ≥0\ge 0≥0 个苹果。
这个东西可以使用组合数学或者是枚举。我们小学五年级就学过了。
问题升级了一下:
- 有 555 的苹果,333 个小朋友,第 iii 个小朋友分到 ≥i\ge i≥i 个苹果。
我们可以通过先给第 iii 个小朋友 iii 个苹果来将这个问题转换成上面的问题。
那么对于有上下界的网络流问题,我们也是这样子解决的。
对于这个网络图:
我们可以将这个网络图拆成两个边权不同的网络图,一个是每条边的容量下界,一个是每条边的容量上界减去容量下界:
第一个网络图在跑完网络流之后,必须要每一个边都是饱和的(也就是该用的都用上了)。
而第二个网络图没有特殊规定。随便走都可以得到一个可行流。
第二个网络图就直接就是普通的网络流了,关键在于如何保证第一个网络图的每一条边都用完了。
我们可以设立两个虚点。一个是超源点 SSS,一个是超汇点 TTT。
对于点 111,我们希望它进入 444 的流,出去 222 的流。所以我们可以从 S→1S \to 1S→1 连一条边权为 4−2=24-2=24−2=2 的边。
对于点 333,我们希望它进入 333 的流,出去 444 的流。所以我们可以从 3→T3 \to T3→T 连一条边权为 4−3=14-3=14−3=1 的边。
对于点 222,我们希望它进入 222 的流,出去 333 的流。所以我们可以从 2→T2 \to T2→T 连一条边权为 3−2=13-2=13−2=1 的边。
就是这个图片。
为什么要这样呢?这样我们是为了对于每一个点,不够出的都出去,不够入的都进入。
如果一个点出去的流容量大于进来的流容量,就说明需要补的是进来的流容量,也就是用这个点向 TTT 连边。否则是从 SSS 向这个点连边。
我先说一下我们要干什么,然后再说明一下我们为什么要这么干。
然后我要做一件事情,把超源点和超汇点搬到第二个网络图,并且保留边和边权。
然后对第二个网络图跑最大流。得出最终的最大流流量 fff。
现在我们可以判断可行性:如果 fff 同时等于 SSS 的出边边权之和,也等于 TTT 的入边边权之和,则该上下界网络图可以找到一种流分配方案。 否则一定不能。
注意,这里的最大流只是用来判断可行性的,和上下界网络最大流完全没有任何关系。
那这样为什么可以呢?
我们可以想象,在第一个网络图中,为了使 111 的入边容量之和等于其出边容量之和,我们让 111 硬灌了 222 的入边流量。
而如果在第二个网络图中,存在方法使这个硬灌的流量被完全消耗掉,就说明这个时候 111 确实可以通过 SSS 灌 222 的入边容量,那这个时候一定存在一种可行流。
硬灌某一个点的出边容量也是同理的。
那为社么要保证最大流呢?
其实我们这个时候不必求最大流,而是只需要保证 SSS 的出边和 TTT 的入边是满流的即可, 但是最大流显然是最有可能满足条件的,所以采用最大流肯定不会更差。
那为什么要在第二个图里面跑最大流呢?
因为我们一开始只跑第一个图的三条白色边肯定是不行的,这样并不能保证一定可以把所有的边的容量都用完。
而接下来我们只能寄希望于第二个图里面的剩余的可以使用的流量来收拾烂摊子,把第一个图里面的下界给达到。
但是我们不止需要判断可行性,而是需要求出可行流!!!
怎么办呢?我们可以求出来第二张图跑最大流的时候所有的增广路:
然后把这些增广路上面通过的流都加到第一张图的里面:
这样我们就可以求出来一种可行流。
好了,怎么找出可行流的方法搞完了,考虑找到可行流中的最大流。
有源和汇的可行流
这个时候不止有点,还有了源点和汇点。而且边还有上下界。
很容易发现,对于多源多汇的图,我们仍然可以通过建超源点和超汇点来把他们变成单源单汇,所以我们只讨论单源单汇即可。
这个时候虽然有了起点和终点限制,但是我们仍然可以建超源点和超汇点。
我们可以继续使用上面的方法连边:
注意我们连这种边的时候,我们要把 s,ts,ts,t 都看成普通的点。
但是注意我们这个时候并不能使问题得到解决,因为 sss 必须是起点,ttt 必须是终点,比无源汇的上下界可行流还复杂一些。
于是,我们只需要略施小计,从 ttt 连一条边权为 +∞+ \infty+∞ 的边到 sss。这样就可以了。
为什么?
假设在过程中 ttt 多了 333 的流量,而 sss 少了 333 的流量。这个东西本来是允许的,但是在跑算法的时候却不允许。怎么办呢?
我们就可以从 ttt 到 sss 连一条边权为正无穷的边,相当于从 ttt 向 sss 缴纳 333 的流量。
这样的话,算法就允许我们在现实允许的所有东西了。
这样就转换为了一个无源汇的可行流问题,可以直接使用上面的办法来判断可行性,也可以求出可行流了。
有源有汇的上下界最大流
求出来可行流之后,我们怎么办呢?
结论本身非常简单粗暴。是这样的:此时在残余网络上直接算 s→ts\to ts→t 的最大流再加上找可行流的时候得出来的最大流即可。
为什么呢?
我们可以这样想:
刚才我们使用无源汇的可行流求解方法求的是最小流,而此时第二个网络图已经为了让第一个网络图出的流和入的流平衡牺牲了一些容量。
这个时候我们也可以把第二个网络图的可调节的部分也消耗完毕,也就是在残余网络上算 s→ts \to ts→t 的最大流加上原来的最大流,这个时候就得出来了最大流。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, s, t;
const int N = 210;
struct edge {
int to, val;
int id;
};
vector<edge> v[N];
int d[N];
void bfs(int s) {
memset(d, -1, sizeof d);
queue<int> q;
q.push(s), d[s] = 0;
while (!q.empty()) {
int f = q.front();
q.pop();
for (auto [to, val, id] : v[f])
if (d[to] == -1 && val > 0)
d[to] = d[f] + 1, q.push(to);
}
}
int cur[N];
int dfs(int u, int t, int fl) {
if (u == t)
return fl;
for (int i = cur[u]; i < (int)v[u].size(); i = ++cur[u])
if (d[v[u][i].to] == d[u] + 1 && v[u][i].val > 0) {
int f = dfs(v[u][i].to, t, min(fl, v[u][i].val));
if (f > 0) {
v[u][i].val -= f, v[v[u][i].to][v[u][i].id].val += f;
return f;
} else
d[v[u][i].to] = -1;
}
return 0;
}
int sum[N];
void add(int x, int y, int w) {
v[x].push_back({y, w, v[y].size()});
v[y].push_back({x, 0, v[x].size() - 1});
}
int dinic(int s, int t) {
int ans = 0;
while (1) {
int x = 0;
bfs(s);
if (d[t] == -1)
return ans;
memset(cur, 0, sizeof cur);
while ((x = dfs(s, t, 9e18)) > 0)
ans += x;
}
return 0;
}//最大流板子
signed main() {
ios::sync_with_stdio(0);
cin >> n >> m >> s >> t;
for (int i = 1; i <= m; i++) {
int x, y, l, w;
cin >> x >> y >> l >> w;
add(x, y, w - l);//连边
sum[x] += l, sum[y] -= l;//这里使用 sum 来记录入边的容量和 和 出边的容量和 的 差。
}
add(t, s, 9e18);//t 到 s 还需要连一条边权正无穷的边
int a = 0;
for (int i = 1; i <= n; i++)
if (sum[i] > 0)
add(i, n + 2, sum[i]), a += sum[i];//这里设 n+1 为超源点,n+2 为超汇点
else if (sum[i] < 0)
add(n + 1, i, -sum[i]);//连边
int ans = dinic(n + 1, n + 2);//先跑一遍可行性
if (a != ans)
cout << "please go home to sleep\n";//发现不可行
else
cout << dinic(s, t) << endl;//否则得到答案
return 0;
}
有源有汇的上下界最小流
我们说过上下界网络图不止有最大流还有最小流,那么最小流该怎么求呢?
首先我们应该满足下限的要求,要不然这甚至不是一个可行流。
很容易发现可行流不一定是最小流。 所以需要从可行流中减去一个值。
具体是那个值呢?就是残余网络上面 t→st \to st→s 的最大流。
为什么这样是可行的?
首先我们要思考:减去这样 t→st \to st→s 的最大流会使得实际方案不合法吗?
显然不会。因为是在残余网络上面跑的,而这个时候所有 s→ts \to ts→t 的反边(相当于 t→st \to st→s 的正边)都是剩余的可以分配的流。
然后我们就会发现:减去残余网络上 t→st \to st→s 的最大流不就是一个贪心的退流的过程吗?
于是最终得出来的就是最小流了。
但是这个时候还是有一个特坑的点,写模板的时候一定要注意啊!!!
注意,这个时候 t→st \to st→s 的那条边权为正无穷的边一定要删掉,要不然就是错的。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, s, t;
const int N = 50010;
struct edge {
int to, val;
int id;
};
vector<edge> v[N];
int d[N];
void bfs(int s) {
memset(d, -1, sizeof d);
queue<int> q;
q.push(s), d[s] = 0;
while (!q.empty()) {
int f = q.front();
q.pop();
for (auto [to, val, id] : v[f])
if (d[to] == -1 && val > 0)
d[to] = d[f] + 1, q.push(to);
}
}
int cur[N];
int dfs(int u, int t, int fl) {
if (u == t)
return fl;
for (int i = cur[u]; i < (int)v[u].size(); i = ++cur[u])
if (d[v[u][i].to] == d[u] + 1 && v[u][i].val > 0) {
int f = dfs(v[u][i].to, t, min(fl, v[u][i].val));
if (f > 0) {
v[u][i].val -= f, v[v[u][i].to][v[u][i].id].val += f;
return f;
} else
d[v[u][i].to] = -1;
}
return 0;
}
int dinic(int s, int t) {
int ans = 0;
while (1) {
int x = 0;
bfs(s);
if (d[t] == -1)
return ans;
memset(cur, 0, sizeof cur);
while ((x = dfs(s, t, 9e18)) > 0)
ans += x;
}
return 0;
}
int sum[N];
void add(int x, int y, int w) {
v[x].push_back({y, w, v[y].size()});
v[y].push_back({x, 0, v[x].size() - 1});
}
signed main() {
ios::sync_with_stdio(0);
cin >> n >> m >> s >> t;
for (int i = 1; i <= m; i++) {
int x, y, l, w;
cin >> x >> y >> l >> w;
add(x, y, w - l);
sum[x] += l, sum[y] -= l;
}
add(t, s, 9e18);
int a = 0;
for (int i = 1; i <= n; i++)
if (sum[i] > 0)
add(i, n + 2, sum[i]), a += sum[i];
else if (sum[i] < 0)
add(n + 1, i, -sum[i]);
if (a != dinic(n + 1, n + 2))
cout << "please go home to sleep\n";//上面的和有源汇上下界最大流一模一样
else {
int ans = 0;
for (auto &[to, val, id] : v[s])
if (to == t)
ans = max(ans, val);//求出来 s 到 t 的可行流,
for (auto &[to, val, id] : v[t])
if (to == s && val > 1e13)//注意这个时候 t 到 s 的那条边权正无穷的边这个时候可能不再是 9e18,因为有一部分被用到反边上面去了
val = 0, v[s][id].val = 0;//删去这条边权极大的边
cout << ans - dinic(t, s) << endl;//得到答案
}
return 0;
}