网络流学习笔记

网络流学习笔记

网络流资料

最大流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

  • 转化思想: 将问题转化为模型(最小点权覆盖集, 最大点权独立集, 最大权闭合子图)

posted @ 2019-11-29 09:27  Hs-black  阅读(304)  评论(0编辑  收藏  举报