结合了我个人理解和y总的内容,这里给一种相对感性的理解方式
当然如果我有错误的地方请dalao指导我(逃)
题目描述
给定一个包含 n 个点 m 条边的有向图,每条边都有一个流量下界和流量上界。
给定源点 S 和汇点 T,求源点到汇点的最大流。
输入格式
第一行包含四个整数 n,m,S,T。
接下来 m 行,每行包含四个整数 a,b,c,d 表示点 a 和 b 之间存在一条有向边,该边的流量下界为 c,流量上界为 d。
点编号从 1 到 n。
输出格式
输出一个整数表示最大流。
如果无解,则输出 No Solution。
数据范围
1 ≤ n ≤ 202,
1 ≤ m ≤ 9999,
1 ≤ a,b ≤ n,
0 ≤ c ≤ d ≤ $10^5$
样例
10 15 9 10
9 1 17 18
9 2 12 13
9 3 11 12
1 5 3 4
1 6 6 7
1 7 7 8
2 5 9 10
2 6 2 3
2 7 0 1
3 5 3 4
3 6 1 2
3 7 6 7
5 10 16 17
6 10 10 11
7 10 14 15
//ans: 43
- 为什么要建新图?
因为此题有上下界,根据我们上题的经验,需要建虚拟源点(S、T)补流;
但是原图有自带的源汇点(s、t),源汇点不保证流量守恒(其他点都保证),所以需要补一条 t —— s 的满容量边,让原图流量守恒,只有满足 {流量守恒容量上限} 我们才可以这样建图。
还记得上题的结论吗,建出这个新图的意义在于:
在这种状态下建出的新图,可以发现若要新图的流对应原图的可行流,需要保证虚拟源点S的出边都满流;
反过来,只要保证新图的流是满流,我们就可以说这个流是原图的一个可行流。
- 建了这个新图,在这种满流的状态下我们可以发现一些很特殊的性质:
任意两个新图的满流相减(流量相减),S出、T入 这些边的流量相同会抵消掉,剩下的流量居然是新图中 s —— t 的流量(这里s、t可以换成任意两点,只要不是S、T就行,但我们此题要求的与s、t有关)
我们设新图两个满流为 f1、f2,可以得到公式 $|f1-f2|=f_{s-t}$,移下项,$f1 = f2 + f_{s-t}$;
一开始说过新图的流对应原图可行流,所以 f1 是原图的一个可行流;
若要 f1 最大,因为 f2 固定了(满流),所以要尽可能让 $f_{s-t}$ 大
要求 $max(f_{s-t})$ 就在新图上跑个 $dinic_{s-t}$ 即可,不过要注意删掉 t —— s
的边,不然流量一直循环求不出来
最后答案 $ans = 满流f2 + max(f_{s-t})$
码字不易点个赞吧^-^
$Code:$
#include<bits/stdc++.h>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define rfor(a,b,c) for(int a=b;a>=c;a--)
#define endl "\n"
//[博客地址]:https://blog.csdn.net/weixin_51797626?t=1
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
double DNF = 1e17;
const int N = 210, M = 40010, MM = N;
int INF = 0x3f3f3f3f, mod = 998244353;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int h[N], e[M], ne[M], f[M], idx;
int dep[N], cur[N], aa[N];
int s, t, tot;
void add(int a, int b, int c, int d) {
e[idx] = b, f[idx] = d - c, ne[idx] = h[a], h[a] = idx++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}
bool bfs() {
queue<int> q;
q.push(S);
mem(dep, -1);
dep[S] = 0, cur[S] = h[S];
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dep[j] == -1 && f[i]) {
dep[j] = dep[t] + 1;
cur[j] = h[j];
if (j == T)return true;
q.push(j);
}
}
}
return false;
}
int find(int u, int limit) {
if (u == T)return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {
cur[u] = i;
int j = e[i];
if (dep[j] == dep[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if (!t)dep[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int r = 0, flow;
while (bfs())while (flow = find(S, INF))r += flow;
return r;
}
int main() {
cinios;
cin >> n >> m >> s >> t;
mem(h, -1);
S = n + 1, T = S + 1;
forr(i, 1, m) {
int a, b, c, d;
cin >> a >> b >> c >> d;
add(a, b, c, d);
aa[a] -= c, aa[b] += c;
//bug —— aa[a] += c, aa[b] -= c;
//注意这里 c 的定义是少流入和少流出
}
//因为有上下界所以建虚拟源点S、T补流
forr(i, 1, n)
if (aa[i] > 0)add(S, i, 0, aa[i]), tot += aa[i];
else if (aa[i] < 0)add(i, T, 0, -aa[i]);
add(t, s, 0, INF);//但是原图有自带的源汇点,源汇点不保证流量守恒(其他点都保证)
//所以需要补一条 t——s 的满容量边,让原图流量守恒
if (dinic() < tot)cout << "No Solution";
else {
int res = f[idx - 1];
S = s, T = t;
f[idx - 1] = f[idx - 2] = 0;
cout << res + dinic();
}
return 0;
}
/*
*/
谢谢你,我终于理解了
谢谢
太棒了,思路清晰简洁,看视频没太看懂,看这个一下就懂了。
但是f1-f2真的一定满足限制条件吗
请问公式中的$f1$指的是新图的流量,为什么在算原图中的流量的时候不需要再加回每条边的容量下界啊? 谢谢
写的真不错,勉强看懂了