网络流学习笔记
网络流学习笔记
网络流资料
最大流dinic
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int INF = 1e9;
const int N = 10005;
const int M = 200005;
int to[M], ne[M];
int w[M], h[N], tot = -1;
inline void add(int x,int y,int z) {
ne[++tot] = h[x], h[x] = tot;
to[tot] = y, w[tot] = z;
}
template <typename T>
void read(T &x) {
x = 0; int f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
if (f) x = -x;
}
int n, m, s, t;
int maxflow;
int depth[N], cur[N];
queue<int> q;
bool bfs(void) {
memset(depth, 0x7f, sizeof(depth));
while (q.size()) q.pop();
for (int i = 1;i <= n; i++) cur[i] = h[i];
depth[s] = 0; q.push(s);
while (q.size()) {
int x = q.front(); q.pop();
for (int i = h[x]; ~i; i = ne[i]) {
int y = to[i];
if (depth[y] > INF && w[i]) {
depth[y] = depth[x] + 1;
q.push(y);
}
}
}
return depth[t] <= INF;
}
int dfs(int now, int limit) {
if (!limit || now == t) return limit;
int flow = 0, f;
for (int i = cur[now]; ~i; i = ne[i]) {
int y = to[i]; cur[now] = i;
if (depth[y] != depth[now] + 1) continue;
f = dfs(y, min(limit, w[i]));
if (!f) continue; flow += f; limit -= f;
w[i] -= f, w[i ^ 1] += f;
if (!limit) break;
}
return flow;
}
int main() {
read(n), read(m), read(s), read(t);
memset(h, -1, sizeof(h));
for (int i = 1;i <= m; i++) {
int x, y, z; read(x), read(y), read(z);
add(x, y, z); add(y, x, 0);
}
while (bfs()) maxflow += dfs(s, INF);
printf ("%d\n", maxflow);
return 0;
}
费用流dinic
注意:
- 反向边费用为负
- dfs时用v标记每个点有没有被标记
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N = 50050;
const int M = 500500;
const int INF = 0x3f3f3f3f;
int read(void) {
int x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return f ? -x : x;
}
int h[N], to[M], ne[M];
int cost[M], w[M], tot = 1;
inline void add(int x,int y,int z,int k) {
ne[++tot] = h[x], h[x] = tot;
to[tot] = y, w[tot] = z, cost[tot] = k;
}
int n, m, s, t;
int dis[N], cur[N];
int v[N];
queue<int> q;
bool spfa(void) {
memset(dis, 0x3f, sizeof(dis));
memset(v, 0, sizeof(v));
q.push(s); dis[s] = 0;
while (q.size()) {
int x = q.front(); q.pop();
v[x] = 0;
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; if (!w[i]) continue;
if (cost[i] + dis[x] < dis[y]) {
dis[y] = cost[i] + dis[x];
if (!v[y]) {
v[y] = 1;
q.push(y);
}
}
}
}
if (dis[t] >= INF) return 0;
for (int i = 1;i <= n; i++) cur[i] = h[i];
return 1;
}
int dfs(int x,int lim) {
if (lim <= 0 || x == t) return lim;
int res = 0; v[x] = 1;
for (int i = cur[x]; i; i = ne[i]) {
int y = to[i]; cur[x] = i;
if (v[y] || dis[y] != dis[x] + cost[i]) continue;
int f = dfs(y, min(lim, w[i]));
w[i] -= f, w[i^1] += f;
res += f, lim -= f;
if (lim <= 0) return res;
}
return res;
}
long long ans1, ans2;
int main() {
freopen("hs.in","r",stdin);
n = read(), m = read(), s = read(), t = read();
for (int i = 1;i <= m; i++) {
int x = read(), y = read(), z = read(), k = read();
add(x, y, z, k);
add(y, x, 0, -k);
}
while (spfa()) {
int tmp = dfs(s, INF);
ans1 += tmp, ans2 += tmp * dis[t];
}
cout << ans1 << ' ' << ans2 << endl;
return 0;
}
无源汇有上下界可行流
先让每条边流量设为最小值, 发现可能流入流出量并不平衡
但是没问题, 反手一个转化, 要将网络流图加一个附加网络流图, 每条边的流量为最大流量-最小流量
对于每一个点, 都有一个A[i]表示其流入流量与流出流量的差
若A[i] > 0, 说明流多了, 要往外流, 附加流的流入量要小于流出量, 否则就是附加流的流入量要大与流出量
那么我们新建一个超级源点s和超级汇点t
如果A[i]>0, 连一条从s到i的流量为A[i]的边, 否则连一条从i到t的流量为-A[i]的边
跑一遍最大流, 如果最大流等于s的出边流量之和, 那么满足题意, 每条边的流量加上原来的最小流量即为所求
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 400005;
int h[N], ne[N], to[N];
int w[N], tot = 1;
inline void add(int x,int y,int z) {
ne[++tot] = h[x], h[x] = tot;
to[tot] = y, w[tot] = z;
}
int read(void) {
int x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
if (f) return -x;
return x;
}
int n, m;
int a[N], ans[N];
int num[N];
int sum, s, t;
const int INF = 0x7fffffff;
int cur[405], dep[405];
queue<int> q;
bool bfs(void) {
memset(dep, 0, sizeof(dep));
q.push(s); dep[s] = 1;
while (q.size()) {
int x = q.front(); q.pop();
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; if (!w[i] || dep[y]) continue;
dep[y] = dep[x] + 1;
q.push(y);
}
}
if (!dep[t]) return false;
for (int i = 1;i <= n+4; i++) cur[i] = h[i];
return true;
}
int flow = 0;
int dfs(int x,int lim) {
if (x == t) return lim;
int res = 0;
for (int i = cur[x]; i; i = ne[i]) {
int y = to[i]; cur[x] = i;
if (!w[i] || dep[x] + 1 != dep[y]) continue;
int f = dfs(y, min(lim, w[i]));
w[i] -= f, w[i^1] += f;
res += f, lim -= f;
if (!lim) return res;
}
return res;
}
int main() {
n = read(), m = read();
for (int i = 1;i <= m; i++) {
int x = read(), y = read(), l = read(), r = read();
ans[i] = l; a[y] += l, a[x] -= l;
add(x, y, r - l); add(y, x, 0);
num[i] = tot;
}
s = n + 1, t = n + 2;
for (int i = 1;i <= n; i++) {
if (a[i] > 0) {
sum += a[i];
add(s, i, a[i]);
add(i, s, 0);
}
else {
add(i, t, -a[i]);
add(t, i, 0);
}
}
while (bfs()) flow += dfs(s, INF);
if (sum != flow) {
cout << "NO\n";
return 0;
}
cout << "YES\n";
for (int i = 1;i <= m; i++) printf ("%d\n", ans[i] + w[num[i]]);
return 0;
}
有源汇有上下界可行流 (最大流/最小流)
有汇源有上下界的可行流
设S, T为超级源点和超级汇点, s, t 为源点和汇点
埋伏他一手, 首先把有汇源问题转化为无汇源问题, 闷声发大财
因为源点和汇点入流和出流不平衡, 从t向s连一条无穷大的边使它平衡, 然后在跑上一问题
设tmp = t 到 s的边的流量, res1 = 拆掉无穷大的边从s到t所跑的最大流, res2 = 拆掉无穷大的边从t到s所跑的最大流
最大流, ans = tmp + res1
思路: 先找到一个可行流, 在残余网络中在跑一遍最大流, 使不能产生新流
最小流, ans = tmp - res2
思路: 先找到一个可行流, 从t到s取消流量, 即回退可行流中可以退的流.
最大流
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 400005;
int h[N], ne[N], to[N];
int w[N], tot = 1;
inline void add(int x,int y,int z) {
ne[++tot] = h[x], h[x] = tot;
to[tot] = y, w[tot] = z;
}
int read(void) {
int x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
if (f) return -x;
return x;
}
int n, m;
int a[N];
int num[N];
int sum, ss, tt;
const int INF = 0x7fffffff;
int cur[405], dep[405];
queue<int> q;
bool bfs(int s,int t) {
memset(dep, 0, sizeof(dep));
q.push(s); dep[s] = 1;
while (q.size()) {
int x = q.front(); q.pop();
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; if (!w[i] || dep[y]) continue;
dep[y] = dep[x] + 1;
q.push(y);
}
}
if (!dep[t]) return false;
for (int i = 1;i <= n+4; i++) cur[i] = h[i];
return true;
}
int flow = 0;
int dfs(int x,int lim,int t) {
if (x == t) return lim;
int res = 0;
for (int i = cur[x]; i; i = ne[i]) {
int y = to[i]; cur[x] = i;
if (!w[i] || dep[x] + 1 != dep[y]) continue;
int f = dfs(y, min(lim, w[i]), t);
w[i] -= f, w[i^1] += f;
res += f, lim -= f;
if (!lim) return res;
}
return res;
}
int ans, s, t;
int main() {
n = read(), m = read(), ss = read(), tt = read();
for (int i = 1;i <= m; i++) {
int x = read(), y = read(), l = read(), r = read();
a[y] += l, a[x] -= l;
add(x, y, r - l); add(y, x, 0);
num[i] = tot;
}
s = n + 1, t = n + 2;
for (int i = 1;i <= n; i++) {
if (a[i] > 0) {
sum += a[i];
add(s, i, a[i]);
add(i, s, 0);
}
else {
add(i, t, -a[i]);
add(t, i, 0);
}
}
add(tt, ss, INF);
while (bfs(s, t)) flow += dfs(s, INF, t);
if (sum != flow) {
cout << "please go home to sleep\n";
return 0;
}
flow = w[tot^1];
w[tot] = w[tot^1] = 0;
while (bfs(ss, tt)) {
dep[n+1] = dep[n+2] = INF;
flow += dfs(ss, INF, tt);
}
cout << flow << endl;
return 0;
}
最小流
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
const int N = 800005;
int h[N], ne[N], to[N];
ll w[N], tot = 1;
inline void add(int x,int y,ll z) {
ne[++tot] = h[x], h[x] = tot;
to[tot] = y, w[tot] = z;
}
ll read(void) {
ll x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
if (f) return -x;
return x;
}
int n, m;
ll a[N];
ll num[N];
ll sum, ss, tt;
const ll INF = 0x7fffffffffff;
ll cur[N], dep[N];
queue<int> q;
bool bfs(int s,int t) {
memset(dep, 0, sizeof(dep));
q.push(s); dep[s] = 1;
while (q.size()) {
int x = q.front(); q.pop();
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; if (!w[i] || dep[y]) continue;
dep[y] = dep[x] + 1;
q.push(y);
}
}
if (!dep[t]) return false;
for (int i = 1;i <= n+4; i++) cur[i] = h[i];
return true;
}
ll flow = 0;
int dfs(int x,ll lim,int t) {
if (x == t) return lim;
ll res = 0;
for (int i = cur[x]; i; i = ne[i]) {
int y = to[i]; cur[x] = i;
if (!w[i] || dep[x] + 1 != dep[y]) continue;
int f = dfs(y, min(lim, w[i]), t);
w[i] -= f, w[i^1] += f;
res += f, lim -= f;
if (!lim) return res;
}
return res;
}
ll ans, s, t;
int main() {
n = read(), m = read(), ss = read(), tt = read();
for (int i = 1;i <= m; i++) {
ll x = read(), y = read(), l = read(), r = read();
a[y] += l, a[x] -= l;
add(x, y, r - l); add(y, x, 0);
num[i] = tot;
}
s = n + 1, t = n + 2;
for (int i = 1;i <= n; i++) {
if (a[i] > 0) {
sum += a[i];
add(s, i, a[i]);
add(i, s, 0);
}
else {
add(i, t, -a[i]);
add(t, i, 0);
}
}
add(tt, ss, INF);
while (bfs(s, t)) flow += dfs(s, INF, t);
if (sum != flow) {
cout << "please go home to sleep\n";
return 0;
}
flow = w[tot^1];
w[tot] = w[tot^1] = 0;
while (bfs(tt, ss)) {
dep[n+1] = dep[n+2] = INF;
flow -= dfs(tt, INF, ss);
}
cout << flow << endl;
return 0;
}
最大权闭合子图
一个有向图的闭合图是该有向图的一个点集, 且点集所有的出边所指向的点还在该点集
给每个点分配一个权值, 一个闭合图中点权和最大的叫做最大权闭合子图
由定义可知, 闭合图中可能包含不只一个连通块
可以用网络流来解决, 首先建图
i的点权为w[i]
超级源点s向所有点权为正的点连一条边权为w[i]的边, 超级汇点t向所有点权为负的点连一条边权为-w[i]的边
对于原来有向图上的每条边(u, v), 在网络图上连一条容量为INF的边(u, v), 答案是正点权和减去最小割
最大闭合子图就是与超级源点在同一连通块的点集
证明(proof)
首先, 最小割割断的只能是与源点和汇点相连的边, 因为割断无穷大显然是不优的
性质: 闭合图和简单割互相对应
以下均为绝对值:
S连接的割边分割后在T集边权为正的点的权值和S1
S连接的割边分割后在T集边权为负的点的权值和S2
T连接的割边分割后在S集边权为正的点的权值和T1
T连接的割边分割后在S集边权为负的点的权值和T2
所以最小割的权值为S1 + T2, 最大闭合子图权值为正权减去负权= T1 - T2
相加即为T1 + S1 = 正点权之和
得证
例题
最大密度子图
定义: 一个无向图(V, E) 的密度g = \(\frac{|E|}{|V|}\)
最大密度子图即最大化无向图的密度
考虑分数规划, 二分g的值
设计函数\(H(x) = max \{\sum_{a\in E}1 - \sum_{b \in V}x\}\) 若H(g) > 0 说明密度大于g, else 密度小于等于g
可以证明二分的范围是 \(\frac{|E|}{|V|}\) 到 m, 精度是\(\frac{1}{n^2}\)
反手一步转化, 每条边(u, v)的存在条件是u和v已经存在, 所以考虑最大闭合子图, 8将边化为权值为1的点, 分别向u, v连一条边权为INF的边, 点权为-g, 用上一方法即可
复杂度 \(\Theta(log_nmaxflow(n+m,n+m))\)
此算法还可以继续改进为\(\Theta(log_nmaxflow(n,n+m))\), 详见文头资料
拓展: 带点权和边权也可以哦
二分图的最小点权覆盖集和最大点权独立集
覆盖集: 所有边至少一个端点在覆盖集中
独立集: 一坨点两两之间没有连边
对于边(u, v) 连一条INF边, s向左部点连边权为点权的边, 右部点向t连边权为点权的边
对于一条简单路径s -> u -> v -> t, 至少要割断一条边
u - > v是不可能被割断的, 被割断的一定是s -> u, v -> t, 代表u, v中最多选一个, 删其边代表不选他, 那么最大点权独立集为点权和-最小割, 最小割对应最小点权覆盖集
网络流24题中的思想与解题方案
-
拆点思想: 一个物品/时间拆成两个点, 两点之间连边表示此物品的使用次数, 费用
-
分层图思想: 按时间(或其他)分层, 每次新建一个单位时间的图, 在残余网络上跑dinic
-
转化思想: 将问题转化为模型(最小点权覆盖集, 最大点权独立集, 最大权闭合子图)