网络流学习计划

EK就跳过了,计划noip前尽量做完网络流24题吧。

//上面这个flag已弃。。。现在主要任务是水dp题
//偶尔会肝一道防止忘记板子

【模板】网络最大流 dinic

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 1000010
#define INF 999999999
using namespace std;

struct fls {
	int to, next, val;
}edge[MAXN << 1];

int head[MAXN << 1], level[MAXN], q[MAXN], n, m, S, T, cnt, t, w;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w) {
	edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}

inline bool BFS () {
	memset (level, -1, sizeof level);
	memset (q, 0, sizeof q);
	level[S] = 0, q[t = w = 1] = S;
	while (t <= w) {
		int now = q[t];
		for (register int i = head[now]; i != -1; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val != 0 && level[v] == -1) {
				level[v] = level[now] + 1;
				q[++w] = v;
			}
		}
		t++;
	}
	if (level[T] != -1) return 1;
	return 0;
}

int DFS (int now, int flow) {
	int ret = flow; if (now == T) return flow;
	for (register int i = head[now]; i != -1; i = edge[i].next) {
		int v = edge[i].to; if (ret <= 0) break;
		if (edge[i].val != 0 && level[now] + 1 == level[v]) {
			int o = DFS (v, min (edge[i].val, ret));
			ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
		}
	}
	return flow - ret;
}

void dinic () {
	int ans = 0;
	while (BFS () == 1) ans += DFS (S, INF);
	printf ("%d\n", ans);
}

int main () {
	memset (head, -1, sizeof head), cnt = 1;
	n = read (), m = read (), S = read (), T = read ();
	for (register int i = 1; i <= m; i++) {
		int u = read (), v = read (), w = read ();
		connect (u, v, w), connect (v, u, 0);
	}
	dinic (); return 0;
}

【模板】二分图匹配 dinic

二分图dinic O ( n 2 n ) O(n^2\sqrt n) O(n2n ) 吊打暴力匈牙利 O ( n 3 ) O(n^3) O(n3)
思路:建立超级源点 S ( 0 ) S(0) S(0),超级汇点 T ( n 1 + n 2 + 1 ) T(n1+n2+1) T(n1+n2+1)
S S S向二分图左部连边,二分图右部向 T T T连边,二分图内部按要求互相连边。
所有正向边边权为1,反向边边权为0,跑一边dinic即可。

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 1000010
#define INF 999999999
using namespace std;

struct fls {
	int to, next, val;
}edge[MAXN << 1];

int head[MAXN << 1], level[MAXN], q[MAXN], n, m, S, T, n1, n2, t, w, cnt = 1;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w) {
	edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}

inline bool BFS () {
	memset (level, -1, sizeof level);
	memset (q, 0, sizeof q);
	level[S] = 0, q[t = w = 1] = S;
	while (t <= w) {
		int now = q[t];
		for (register int i = head[now]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val != 0 && level[v] == -1) {
				level[v] = level[now] + 1;
				q[++w] = v;
			}
		}
		t++;
	}
	if (level[T] != -1) return 1;
	return 0;
}

int DFS (int now, int flow) {
	int ret = flow; if (now == T) return flow;
	for (register int i = head[now]; i; i = edge[i].next) {
		int v = edge[i].to; if (ret <= 0) break;
		if (edge[i].val != 0 && level[now] + 1 == level[v]) {
			int o = DFS (v, min (edge[i].val, ret));
			ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
		}
	}
	return flow - ret;
}

void dinic () {
	int ans = 0;
	while (BFS () == 1) ans += DFS (S, INF);
	printf ("%d\n", ans);
}

int main () {
	n1 = read (), n2 = read (), m = read (), S = 0, T = n1 + n2 + 1;
	for (register int i = 1; i <= n1; i++) connect (S, i, 1), connect (i, S, 0);
	for (register int i = n1 + 1; i <= n1 + n2; i++) connect (i, T, 1), connect (T, i, 0);
	for (register int i = 1; i <= m; i++) {
		int u = read (), v = read () + n1;
		if (u > n1 || v > n1 + n2) continue;
		connect (u, v, 1), connect (v, u, 0);
	}
	dinic (); return 0;
}

飞行员配对方案问题 dinic

一开始也是建图二分图上跑dinic,和模板唯一不同的就是需要输出两两匹配的方案。
思路:先排除与超级源点和超级汇点相连的边,因为我们要的是二分图内部的边。
输出方案的话,看看这条边的反向边有没有流量,有流量就是启用了,输出这条边的两端点即可。
不用纠结顺序,有spj

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define INF 999999999
#define MAXN 100010
using namespace std;

struct fls {
	int to, next, val;
}edge[MAXN << 1];

int head[MAXN << 1], q[MAXN], level[MAXN], n1, n2, S, T, t, w, cnt = 1, ans;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w) {
	edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}

inline bool BFS () {
	memset (level, -1, sizeof level);
	level[S] = 0, q[t = w = 1] = S;
	while (t <= w) {
		int now = q[t];
		for (register int i = head[now]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val != 0 && level[v] == -1) {
				level[v] = level[now] + 1;
				q[++w] = v;
			}
		}
		t++;
	}
	if (level[T] == -1) return 0;
	return 1;
}

int DFS (int now, int flow) {
	int ret = flow; if (now == T) return flow;
	for (register int i = head[now]; i; i = edge[i].next) {
		int v = edge[i].to; if (ret <= 0) break;
		if (edge[i].val != 0 && level[now] + 1 == level[v]) {
			int o = DFS (v, min (ret, edge[i].val));
			ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
		}
	}
	return flow - ret;
}

void dinic () {
	while (BFS ()) ans += DFS (S, INF);
}

int main () {
	n1 = read (), n2 = read (), S = 0, T = n1 + n2 + 1;
	for (;;) {
		int u = read (), v = read ();
		if (u == -1 || v == -1) break;
		connect (u, v, 1), connect (v, u, 0);
	}
	for (register int i = 1; i <= n1; i++) connect (S, i, 1), connect (i, S, 0);
	for (register int i = n1 + 1; i <= n1 + n2; i++) connect (i, T, 1), connect (T, i, 0);
	dinic (); if (ans == 0) return puts ("No Solution"), 0;
	printf ("%d\n", ans);
	for (register int i = 2; i <= cnt; i += 2) {
		if (edge[i].to == S || edge[i ^ 1].to == S) continue;
		if (edge[i].to == T || edge[i ^ 1].to == T) continue;
		if (edge[i ^ 1].val != 0) printf ("%d %d\n", edge[i].to, edge[i ^ 1].to);
	}
	return 0;
}

【模板】最小费用最大流 费用流

按照题意连边,反向边流量为0,费用为原边相反数。
一直做 S P F A SPFA SPFA直到汇点 T T T没有新增流量(找不到新的增广路)了为止,统计入答案 maxflow &amp; mincost \text {maxflow~\&amp; mincost} maxflow & mincost
核心内容就是在 S P F A SPFA SPFA松弛操作时保存信息,具体操作是用pre[]数组记录路径(从哪个点来),last[]记录边。

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 100010
using namespace std;

struct fls {
	int to, next, dist, flow;
}edge[MAXN << 1];

int head[MAXN << 1], vis[MAXN], dist[MAXN], flow[MAXN], pre[MAXN], q[MAXN], last[MAXN];
int cnt = 1, n, m, S, T, maxflow, mincost, t, w;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w, int o) {
	edge[++cnt].to = v, edge[cnt].flow = w, edge[cnt].dist = o,
	edge[cnt].next = head[u], head[u] = cnt;
}

inline bool SPFA () {
	memset (dist, 0x3f3f3f3f, sizeof dist);
	memset (flow, 0x3f3f3f3f, sizeof flow);
	memset (vis, 0, sizeof vis);
	q[t = w = 1] = S, vis[S] = 1, dist[S] = 0, pre[T] = -1;
	while (t <= w) {
		int now = q[t];	t++; vis[now] = 0;
		for (register int i = head[now]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].flow > 0 && dist[v] > dist[now] + edge[i].dist) {
				dist[v] = dist[now] + edge[i].dist;
				pre[v] = now; last[v] = i;
				flow[v] = min (flow[now], edge[i].flow);
				if (! vis[v]) {
					vis[v] = 1; q[++w] = v;
				}
			}
		}
	}
	return pre[T] != -1;
}

void MCMF () {
	while (SPFA ()) {
		int now = T;
		maxflow += flow[now], mincost += flow[now] * dist[now];
		while (now != S) {
			edge[last[now]].flow -= flow[T];
			edge[last[now] ^ 1].flow += flow[T];
			now = pre[now];
		}
	}
	printf ("%d %d\n", maxflow, mincost);
}

int main () {
	n = read (), m = read (), S = read (), T = read ();
	for (register int i = 1; i <= m; i++) {
		int u = read (), v = read (), w = read (), o = read ();
		connect (u, v, w, o), connect (v, u, 0, -o);
	}
	MCMF ();
	return 0;
}

负载平衡问题 费用流

本题与[HAOI2008]糖果传递和均分纸牌模板一样
可以解绝对值方程组或贪心 O ( n ) O(n) O(n)解决,考虑费用流,令 a v e = ∑ i = 1 n a i n ave=\frac{\sum_{i=1}^n a_i}{n} ave=ni=1nai,即调整后每个节点的值。
建立源点与汇点,小于 a v e ave ave的需要流,于是连向汇点,大于 a v e ave ave的需要分流,于是从源点连向它。边的流量为与 a v e ave ave的差值,费用为0(因为是虚拟的)
环上的点与它左右两边的点均连边,流量无限,费用为1(可以随便你怎么流,而且流是要花费代价的)
注意环的处理。建完图套 M C M F MCMF MCMF即可

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define INF 999999999
#define MAXN 100000
using namespace std;

struct fls {
	int to, next, flow, dist;
}edge[MAXN << 1];

bool vis[MAXN];
int head[MAXN << 1], dist[MAXN], flow[MAXN], pre[MAXN], q[MAXN], id[MAXN];
int a[MAXN], mincost, S, T, n, t, w, cnt = 1;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w, int o) {
	edge[++cnt].to = v, edge[cnt].flow = w, edge[cnt].dist = o,
	edge[cnt].next = head[u], head[u] = cnt;
}

inline bool SPFA () {
	memset (dist, 0x3f3f3f3f, sizeof dist);
	memset (flow, 0x3f3f3f3f, sizeof flow);
	memset (vis, 0, sizeof vis);
	q[t = w = 1] = S, vis[S] = 1, dist[S] = 0, pre[T] = -1;
	while (t <= w) {
		int now = q[t]; t++; vis[now] = 0;
		for (register int i = head[now]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].flow > 0 && dist[v] > dist[now] + edge[i].dist) {
				dist[v] = dist[now] + edge[i].dist,
				flow[v] = min (flow[now], edge[i].flow),
				pre[v] = now, id[v] = i;
				if (! vis[v]) {
					vis[v] = 1, q[++w] = v;
				}
			}
		}
	}
	return pre[T] != -1;
}

void MCMF () {
	while (SPFA ()) {
		mincost += dist[T] * flow[T];
		int now = T;
		while (now != S) {
			edge[id[now]].flow -= flow[T];
			edge[id[now] ^ 1].flow += flow[T];
			now = pre[now];
		}
	}
	printf ("%d\n", mincost);
}

int main () {
	n = read (), S = 0, T = n + 1; int sum = 0;
	for (register int i = 1; i <= n; i++) a[i] = read (), sum += a[i];
	int ave = sum / n;
	for (register int i = 1; i <= n; i++)
		if (a[i] <= ave) connect (i, T, ave - a[i], 0), connect (T, i, 0, 0);
		else connect (S, i, a[i] - ave, 0), connect (i, S, 0, 0);
	for (register int i = 2; i <= n; i++) {
		connect (i, i - 1, INF, 1), connect (i - 1, i, 0, -1),
		connect (i - 1, i, INF, 1), connect (i, i - 1, 0, -1);
	}
	connect (1, n, INF, 1), connect (n, 1, 0, -1),
	connect (n, 1, INF, 1), connect (1, n, 0, -1);
	MCMF (); return 0;
}

魔术球问题 dinic

又是可以贪心的题,考虑如何建图a
注意到柱子是无关紧要的,显然考虑在小球上面做文章
拆点是一个重要技巧,一个小球拆成两个点,分别连向源点和汇点。
球内如何连边?对于当前编号 n o w now now i i i ( n o w , n o w ∗ 2 ) (\sqrt {now},\sqrt{now*2}) (now ,now2 )内枚举,这样就可以直接找到形成完全平方数的小球, i 2 − n o w i^2-now i2now n o w &lt; &lt; 1 ∣ 1 now&lt;&lt;1|1 now<<11连边即可
如果某次dinic后发现没有新增的流(可以新放上去的小球)那么就新增一根柱子,柱子数大于n时退出。
dinic更新流量时,记录流向当前节点的是哪个点,最后从源点开始输出即可
我的F[]数组记录的是这个编号的小球有没有被放过,Fst[]数组记录的是这根柱子上的第一个球是什么

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#define MAXN 100010
#define INF 999999999
using namespace std;

struct fls {
	int to, next, val;
}edge[MAXN << 1];

bool F[MAXN];
int head[MAXN << 1], pre[MAXN], vis[MAXN], level[MAXN], q[MAXN], Fst[MAXN];
int S = 100001, T = 100002, cnt = 1, n, tot, now, t, w;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline void connect (int u, int v, int w) {
	edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}

inline bool BFS () {
	memset (level, -1, sizeof level);
	q[t = w = 1] = S, level[S] = 0;
	while (t <= w) {
		int now = q[t];
		for (register int i = head[now]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val != 0 && level[v] == -1) {
				level[v] = level[now] + 1;
				q[++w] = v;
			}
		}
		t++;
	}
	return (level[T] != -1);
}

int DFS (int now, int flow) {
	int ret = flow; if (now == T) return flow;
	for (register int i = head[now]; i; i = edge[i].next) {
		int v = edge[i].to; if (ret <= 0) break;
		if (edge[i].val != 0 && level[v] == level[now] + 1) {
			int o = DFS (v, min (ret, edge[i].val));
			ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
			if (o > 0) pre[now >> 1] = v >> 1;
		}
	}
	return flow - ret;
}

inline void dinic () {
	int ans = 0;
	while (BFS ()) ans += DFS (S, INF);
	if (! ans) Fst[++tot] = now;
}

int main () {
	n = read ();
	while (tot <= n) {
		now++;
		connect (S, now << 1, 1), connect (now << 1, S, 0),
		connect (now << 1 | 1, T, 1), connect (T, now << 1 | 1, 0);
		for (register int i = (int) sqrt (now) + 1; (i * i) < (now << 1); i++) {
			connect ((i * i - now) << 1, now << 1 | 1, 1);
			connect (now << 1 | 1, (i * i - now) << 1, 0);
		}
		dinic ();
	}
	printf ("%d\n", now - 1);
	for (register int i = 1; i <= n; i++)
		if (! F[Fst[i]]) {
			for (register int j = Fst[i]; j && j != T >> 1; j = pre[j]) {
				F[j] = 1, printf ("%d ", j);
			}
			puts ("");
		}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值