zoukankan      html  css  js  c++  java
  • 网络流学习笔记

    网络流学习笔记

    网络流资料

    最大流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
    ", 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
    ";
    		return 0;
    	}
    	cout << "YES
    ";
    	for (int i = 1;i <= m; i++) printf ("%d
    ", 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
    ";
    		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
    ";
    		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_{ain 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

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

  • 相关阅读:
    spring注解实现业务层事务管理,当业务层自调用时,事务失效问题解决
    spring的事务
    maven创建web项目
    eclipse安装最新版svn
    .ncx文件剖析
    关闭MongoDB服务的几种方法
    mongodb添加验证用户 删除用户
    高性能kv存储之Redis、Redis Cluster、Pika:如何应对4000亿的日访问量?
    Python中msgpack库的使用
    彻底理解一致性哈希算法(consistent hashing)
  • 原文地址:https://www.cnblogs.com/Hs-black/p/11955736.html
Copyright © 2011-2022 走看看