Loading

「刷题记录」网络流24题

有标题栏,可以直接通过标题栏跳转

1、「网络流 24 题」搭配飞行员

这是最简单的一道题了,题中说一个正驾驶员只能搭配一个副驾驶员,由此,我们可以转化成二分图,这道题就变成了求二分图最大匹配,匈牙利或 Dinic 算法都可以

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 1e4 + 5;

int n, m, S, T, cnt;
ll maxflow;
int h[N], dep[N], cur[N];
bool inque[N];

struct edge {
	int v, nxt;
	ll w;
} e[N << 2];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].nxt = h[u];
	e[cnt].w = w;
	h[u] = cnt;
}

inline bool bfs() {
	queue<int> q;
	for (int i = 0; i <= T; ++ i) {
		dep[i] = 1e9;
		inque[i] = 0;
		cur[i] = h[i];
	}
	q.push(S);
	dep[S] = 0, inque[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

inline ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int main() {
	n = read(), m = read();
	T = n + 1, cnt = 1;
	int u, v;
	while (~(scanf("%d%d", &u, &v))) {
		add(u, v, 1);
		add(v, u, 0);
	}
	for (int i = 1; i <= m; ++ i) {
		add(S, i, 1);
		add(i, S, 0);
	}
	for (int i = m + 1; i <= n; ++ i) {
		add(i, T, 1);
		add(T, i, 0);
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	printf("%lld\n", maxflow);
	return 0;
}

2、「网络流 24 题」太空飞行计划

上来这个题感觉不是很好理解,但代码和建图是真简单
题中说,要使净收入最大,净收入 \(=\) 参与的实验最后全部的收入 \(-\) 安装仪器的费用,我们想,如果我们的最后的收入比最后的安装费用小,那我们就没有必要进行实验(你还赔了钱),如果我们最后的收入比最后的安装费用大,那我们就有净收入
我们先建图,建一个超级源点连向所有的实验,边权为支付的费用,建一个超级汇点连向所有仪器,边权为仪器安装费用,最后流入汇点的是我们进行实验花的钱,这里就用到了最小割,如果安装费用比收入大,那割的是进行实验的边,否则,割的是安装仪器的边,如果收入大于支出(安装费用),那我们的净收入就是总的收入 \(-\) 最小割,否则,净收入就是总的收入 \(-\) 最小割(总的收入)\(= 0\)
这里涉及一个模型:最大权闭合子图
这里还设计一个定理:最大流等于最小割
简单证明一下
最大流的值取决于每条流径中流量最小的那一条边,即最大流相当于是每条流径中流量最小的边的和,而最小割就是求切断后能使图分为两半的边的最小流量,也就相当于每条流径中流量最小的边的和,所以最大流等于最小割
如何保证最小割的答案来源于最大流?如果已经求出最大流了,那么在图中就再也找不到能从源点流向汇点的路径了(否则就是你最大流求错了),所以可以保证最小割的答案来源都在最大流的流径上
最大权闭合子图,就是说你选了一个点 \(u\),那么和 \(u\) 有关系的点(相连的点)都要选,而且要选到底
具体可以参考这边博客最大权闭合子图
代码很好写

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 210;

int n, m, cnt, S, T;
ll sum, maxflow;
int h[N], dep[N], cur[N];
bool inque[N];

struct edge {
	int v, nxt;
	ll w;
} e[N * N << 1];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

inline bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		dep[i] = 1e9;
		cur[i] = h[i];
		inque[i] = 0;
	}
	q.push(S);
	dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

inline ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (dep[v] == dep[u] + 1 && e[i].w) {
			rlow = dfs(v, min(e[i].w, flow - used));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	if (!used)	dep[u] = 1e9;
	return used;
}

int main() {
	m = read(), n = read();
	cnt = 1;
	T = n + m + 1;
	for (int i = 1; i <= m; ++ i) {
		ll x = read();
		char ch;
		sum += x;
		add(S, i, x), add(i, S, 0);
		while (scanf("%lld%c", &x, &ch)) {
			add(i, x + m, 1e18);
			add(x + m, i, 0);
			if (ch == '\n' || ch == '\r')	break;
		}
	}
	for (int i = 1; i <= n; ++ i) {
		ll val = read();
		add(i + m, T, val);
		add(T, i + m, 0);
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	for (int i = 1; i <= m; ++ i) {
		if (dep[i] != 1e9) {
			printf("%d ", i);
		}
	}
	putchar('\n');
	for (int i = 1; i <= n; ++ i) {
		if (dep[i + m] != 1e9) {
			printf("%d ", i);
		}
	}
	putchar('\n');
	printf("%lld\n", sum - maxflow);
	return 0;
}

3、「网络流 24 题」最小路径覆盖

题意很明确,找到最少的不相交的路径,是路径上的点覆盖这个图上所有的点,这里要注意,单独一个点也算是一条路径
建图:我们要进行一个操作——拆点,将一个点 \(i\) 拆成两个 \((i, i + n)\),前者代表别的点流向这个点,后者代表这个点出发流向别的点,由于单独一个点也算是一天路径,所以源点要向所有点 \(i\) 连一条边,权值为 \(1\),所有点再向汇点连一条边,权值也为 \(1\),再根据题目中所说的边的关系建边 \((u + n \rightarrow v)\),跑最大流即可,这个也相当于二分图最大匹配
这里找到最大流后,我们再回到题中,在最开始,我们可以把图中的每一个点都看作是一条路径,这样就有 \(n\) 条路经,每当有两个点连边(被匹配),相当于将两个路径合并,最后路径个数就是 ($n - $最大匹配数),输出路径我用的并查集,注意并查集的父亲的方向
最小路径覆盖可以看看这篇文章:最小路径覆盖

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 210;
const int M = 6e3 + 5;

int n, m, cnt = 1, S, T;
ll maxflow, ans;
int h[N << 1], dep[N << 1], cur[N << 1], f[N << 1];
bool inque[N << 1];

struct edge {
	int v, nxt;
	ll w;
} e[M << 2];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
	e[cnt].w = w;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		dep[i] = 1e9;
		inque[i] = 0;
		cur[i] = h[i];
	}
	q.push(S);
	dep[S] = 0, inque[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (dep[v] == dep[u] + 1 && e[i].w) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	if (!used)	dep[u] = 1e9;
	return used;
}

inline int find(int x) {
	return f[x] == x ? f[x] : f[x] = find(f[x]);
}

inline void Union(int a, int b) {
	int fa = find(a), fb = find(b);
	if (fa != fb)	f[fa] = fb;
}

int main() {
	n = read(), m = read();
	cnt = 1;
	T = n + n + 1;
	for (int i = 1; i <= m; ++ i) {
		int u = read(), v = read();
		add(u, v + n, 1);
		add(v + n, u, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		add(S, i, 1);
		add(i, S, 0);
		add(i + n, T, 1);
		add(T, i + n, 0);
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	ans = n - maxflow;
	for (int i = 1; i <= n; ++ i) {
		f[i] = i;
	}
	for (int u = 1; u <= n; ++ u) {
		for (int i = h[u]; i; i = e[i].nxt) {
			if (!e[i].w && e[i].v - n <= n && e[i].v) {
				Union(u, e[i].v - n);
			}
		}
	}
	for (int i = n; i; -- i) {
		bool fg = 0;
		for (int j = 1; j <= n; ++ j) {
			if (find(j) == i) {
				printf("%d ", j);
				fg = 1;
			}
		}
		if (fg) {
			putchar('\n');
		}
	}
	printf("%lld\n", ans);
	return 0;
}

4、「网络流 24 题」魔术球

这个和上一个题差不多,只不过这个题的建边条件有点不同,如果两个数的和为完全平方数才能建边,每个点后面只能跟一个点,即都是简单路径,如果新节点没法在任何节点后面接上,那就放到新柱子上,即开一条新的路径,由于柱子数为 \(n\),每开一个新路径要用一个柱子,所以当路径数大于 \(n\) 时,代表这个点(当前操作的点)已经放不进去了,在它前面的点就是答案,路径输出还是并查集

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 1e4 + 5;

int n, cnt, T, S;
ll maxflow;
int h[(N << 1) + 2], dep[(N << 1) + 2], cur[(N << 1) + 2], fa[(N << 1) + 2];
bool inque[(N << 1) + 2];
unordered_map<ll, int> mp;

struct edge {
	int v, nxt;
	ll w;
} e[N * 100];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		inque[i] = 0;
		dep[i] = 1e9;
	}
	q.push(S);
	dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (dep[v] == dep[u] + 1 && e[i].w) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int find(int x) {
	return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}

inline void Union(int a, int b) {
	int fx = find(a), fy = find(b);
	if (fx != fy)	fa[fx] = fy;
}

int main() {
	int ans = 0, num = 0;
	n = read();
	cnt = 1;
	T = N + N + 1;
	for (int i = 1; i <= 1000; ++ i) {
		mp[i * i] = 1;
	}
	for (int i = 1; i; ++ i) {
		++ num;
		add(S, i, 1), add(i, S, 0);
		add(i + N, T, 1), add(T, i + N, 0);
		for (int j = 1; j < i; ++ j) {
			if (mp.find(i + j) != mp.end()) {
				add(i, j + N, 1);
				add(j + N, i, 0);
			}
		}
		while (bfs()) {
			dfs(S, 1e18);
		}
		if (num - maxflow > n)	break;
		++ ans;
	}
	printf("%d\n", ans);
	for (int i = 1; i <= ans; ++ i) {
		fa[i] = i;
	}
	for (int u = 1; u <= ans; ++ u) {
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (!e[i].w && v - N <= ans && v) {
				Union(min(u, v - N), max(u, v - N));
			}
		}
	}
	for (int i = 1; i <= ans; ++ i) {
		int fg = 0;
		for (int j = 1; j <= i; ++ j) {
			if (find(j) == i) {
				fg = 1;
				printf("%d ", j);
			}
		}
		if (fg)	putchar('\n');
	}
	return 0;
}

5、「网络流 24 题」圆桌聚餐

这个题其实就很简单了,根据 套路 题意,单位企业肯定要和圆桌连边,题中说每个桌子坐一个单位的代表,所以边权为 \(1\),我们让源点与每一个单位连边,边权为单位的人数,将圆桌与汇点连边,边权为圆桌能容纳的人数,跑最大流即可,这里要注意,我们要求的合法方案是每个人都能按要求(每个单位在每个桌子上只能有一个人)坐下,如果最后返回的最大流不等于总人数,就是无解。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int M = 155;
const int N = 305;

int m, n, S, T, cnt = 1;
ll maxflow, sum;
int h[N + M], cur[N + M], dep[N + M];
bool inque[N + M];

struct edge {
	int v, nxt;
	ll w;
} e[N * M << 2];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
	e[cnt].w = w;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dep[i] = 1e9;
		inque[i] = 0;
	}
	q.push(S);
	inque[S] = 0, dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int main() {
	m = read(), n = read();
	T = n + m + 1;
	for (int i = 1; i <= m; ++ i) {
		ll num = read();
		sum += num;
		add(S, i, num);
		add(i, S, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		ll hid = read();
		add(i + m, T, hid);
		add(T, i + m, 0);
	}
	for (int i = 1; i <= m; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			add(i, j + m, 1);
			add(j + m, i, 0);
		}
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	if (maxflow == sum)	printf("1\n");
	else {
		printf("0\n");
		return 0;
	}
	for (int u = 1; u <= m; ++ u) {
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (!e[i].w && v > m && v <= m + n) {
				printf("%d ", v - m);
			}
		}
		putchar('\n');
	}
	return 0;
}

6、「网络流 24 题」最长递增子序列

这个题。。。怎么说,考的很全面 甚至考到了 DP
第一问就按照 \(\text{DP}\) 做就行了,最长不下降子序列,记录下值,后面要用(值的含义:以这个数字结尾的最长不下降子序列的长度)
第二问,条件是每个数字只能用一次,由于我们不确定最长不下降子序列的起始位置,所以源点要向每个位置 \(i\) 连边,边权为 \(1\),同时又涉及连接前后,所以我们还要拆点,如果一个位置的 DP 值是我们第一问求出的最大值,那么我们就将这个位置 \(i + n\) 向汇点连一条边(毕竟没有更长的了,你也只是统计个数),边权为 \(1\)
现在,源点汇点都连好了,就剩下每个位置之间的连边了,首先,每个点只能用一次,所以我们拆成的两个点之间要连一条边权为 \(1\) 的边,只有流经这条边,才代表用了这个数字,接下来,从前面扫到当前位置,由在最长不下降子序列中可能是这个数的前一个数向这个数连边(说人话就是,他可能排在你前面,那他就向你连一条边,没准你俩能成),判断就是这个前面这个数小于等于当前这个数,并且 \(DP_i + 1 = DP_j\)\(i\) 表示前面的数,\(j\) 表示当前的数,连好后跑最大流
第三问,就是将 \(1 \rightarrow i + n\)\(n \rightarrow n + n\)\(S \rightarrow 1\) 的流量改为无穷大,我们这里还要特判一下,如果这个数列中只有两个数,那么我们就没必要将 \(n \rightarrow n + n\) 的边权设为 \(1\) 了(毕竟再怎么选也只有这两个点,都是一样的),同时还要保证 \(DP_{n + n} =\) 第一问的值(毕竟如果以这个点为结尾没有最长的子序列,那连这个点有何用,增加错误答案?)
再不明白看看代码吧

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 510;

int n, l, cnt = 1, S, T;
ll maxflow;
int s[N], dp[N], h[N << 1], cur[N << 1], dep[N << 1];
bool inque[N << 1];
vector<int> vec;

struct edge {
	int v, nxt;
	ll w;
} e[N * N << 1];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		dep[i] = 1e9;
		inque[i] = 0;
		cur[i] = h[i];
	}
	q.push(S);
	dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int main() {
	n = read();
	T = n + n + 1;
	cnt = 1;
	for (int i = 1; i <= n; ++ i) {
		s[i] = read();
		dp[i] = 1;
	}
	for (int i = 1; i <= n; ++ i) {
		if (vec.empty() || s[i] >= vec.back()) {
			vec.push_back(s[i]);
			dp[i] = (int)vec.size();
		}
		else {
			int pos = upper_bound(vec.begin(), vec.end(), s[i]) - vec.begin(); // 二分
			vec[pos] = s[i];
			dp[i] = pos + 1;
		}
		l = max(l, dp[i]);
	}
	printf("%d\n", l);
	for (int i = 1; i <= n; ++ i) {
		if (dp[i] == 1) {
			add(S, i, 1);
			add(i, S, 0);
		}
		if (dp[i] == l) {
			add(i + n, T, 1);
			add(T, i + n, 0);
		}
		add(i, i + n, 1);
		add(i + n, i, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j < i; ++ j) {
			if (s[j] <= s[i] && dp[j] + 1 == dp[i]) { // 找前一个位置
				add(j + n, i, 1);
				add(i, j + n, 0);
			}
		}
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	printf("%lld\n", maxflow);
	add(1, 1 + n, 1e18);
	add(1 + n, 1, 0);
	add(S, 1, 1e18);
	add(1, S, 0);
	add(n, n + n, 1e18);
	add(n + n, n, 0);
	if (dp[n] == l && n != 1) { // 特判
		add(n + n, T, 1e18);
		add(T, n + n, 0);
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	printf("%lld\n", maxflow);
	return 0;
}

7、「网络流 24 题」试题库

这道题和上面的圆桌问题很想,建一个超级源点,向每一个试题类型连边,边权为这个类型的题要出的题数,每道试题(注意,是题,不是前面的类型)都向汇点连一条边,边权为 \(1\),流经了就说明选了这个题,每个试卷类型再向各自对应的题目连边,边权为 \(1\),然后跑最大流,如果最后最大流不等于 \(m\),说明无解,有解的话,我们通过跑残量图来输出答案,如果选了这个题,那么这条边的反向边的流量一定为 \(1\)(反正不为 \(0\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 1050;

int k, n, S, T, cnt = 1;
ll maxflow, sum;
int h[N], dep[N], cur[N];
bool inque[N];

struct edge {
	int v, nxt;
	ll w;
} e[N * N << 1];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		dep[i] = 1e9;
		cur[i] = h[i];
		inque[i] = 0;
	}
	q.push(S);
	dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			inque[u] = 0;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int main() {
	k = read(), n = read();
	T = k + n + 1;
	cnt = 1;
	for (int i = 1; i <= k; ++ i) {
		ll x = read();
		sum += x;
		add(S, i, x);
		add(i, S, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		int p = read(), x;
		for (int j = 1; j <= p; ++ j) {
			x = read();
			add(x, i + k, 1);
			add(i + k, x, 0);
		}
		add(i + k, T, 1);
		add(T, i + k, 0);
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	if (maxflow != sum) {
		puts("No Solution!");
		return 0;
	}
	for (int i = 1; i <= k; ++ i) {
		printf("%d: ", i);
		for (int j = h[i]; j; j = e[j].nxt) {
			if (!e[j].w && e[j].v) {
				printf("%d ", e[j].v - k);
			}
		}
		putchar('\n');
	}
	return 0;
}

8、「网络流 24 题」方格取数

题目给我们的信息是:你选了一个格,那么这个格的上下左右你都不能选,我们要求最大的和,那么我们也可以使不选的方格的数的和最小来得到最大答案(这是容斥吗?蒟蒻发言),我们来想一下,如果我们将格子里的数转移到边权上,像不像最小割?割断一条边,代表这两个数不会同时选
同时,我们会发现,你选了一个格,那与这个格子同对角线的格子不会受这个格子影响,对吧?在扩展一些,假设这个格子行数加列数是一个奇数,那么所有行数加列数都为奇数的格子都不会受影响,这些格子一定可以同时选,行数加列数为偶数的有的能选,有的不能选
现在,说一下这个题的建图方法,我们将所有格子分为两类,一类是行数加列数为奇数的格子,一类是行数加列数为偶数的格子(每一类中的格子之间可以同时选),将不能同时选的格子连上边,边权为 \(+ \infty\),因为最小割等于最大流,最大流的值取决于每条流径中流量最小的那一条边,而最小割就是求切断后能使图分为两半的边的最小流量,所以设为 \(+ \infty\) 不会影响最小割的取值,也不会割断这一条边,源点向奇数类格子连边,边权为这个格子的权值,偶数类的格子向汇点连边,边权为格子的取值,(如果割断了源点到奇数类的边,说明没选那个奇数类的点;如果割断的是偶数类到汇点的边,说明没选那个偶数类的点)最后用答案减去最大流

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 110;
const int M = ((N * N) << 2);

int m, n, S, T, cnt, cot = 1;
ll maxflow, sum;
int g[N][N], id[N][N], h[N * N], dep[N * N], cur[N * N];
int inque[N * N];

struct edge {
	int v, nxt;
	ll w;
} e[M];

inline void add(int u, int v, ll w) {
	e[++ cot].v = v;
	e[cot].w = w;
	e[cot].nxt = h[u];
	h[u] = cot;
}

bool bfs() {
	queue<int> q;
	for (int i = S; i <= T; ++ i) {
		inque[i] = 0;
		dep[i] = 1e9;
		cur[i] = h[i];
	}
	q.push(S);
	dep[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll used = 0, rlow = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			rlow = dfs(v, min(flow - used, e[i].w));
			if (rlow) {
				used += rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				if (used == flow)	break;
			}
		}
	}
	return used;
}

int main() {
	m = read(), n = read();
	int cj = n * m;
	T = cj + 1;
	for (int i = 1; i <= m; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			g[i][j] = read();
			id[i][j] = ++ cnt;
			sum += g[i][j];
			if ((i + j) & 1) {
				add(S, id[i][j], g[i][j]);
				add(id[i][j], S, 0);
			}
			else {
				add(id[i][j], T, g[i][j]);
				add(T, id[i][j], 0);
			}
		}
	}
	for (int i = 1; i <= m; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			if (!((i + j) & 1))	continue;
			if (i + 1 <= m) {
				add(id[i][j], id[i + 1][j], 1e18);
				add(id[i + 1][j], id[i][j], 0);
			}
			if (j + 1 <= n) {
				add(id[i][j], id[i][j + 1], 1e18);
				add(id[i][j + 1], id[i][j], 0);
			}
			if (i - 1 > 0) {
				add(id[i][j], id[i - 1][j], 1e18);
				add(id[i - 1][j], id[i][j], 0);
			}
			if (j - 1 > 0) {
				add(id[i][j], id[i][j - 1], 1e18);
				add(id[i][j - 1], id[i][j], 0);
			}
		}
	}
	while (bfs()) {
		dfs(S, 1e18);
	}
	printf("%lld\n", sum - maxflow);
	return 0;
}

9、「网络流 24 题」餐巾计划

这道题,费用流的开始
这道题的关系有点复杂,慢慢来,对于每一天,有开始和结束两个时刻,开始时要有足够的干净的餐巾,结束时会有脏的餐巾,对于脏餐巾,可以拖着不洗,也可以送到快洗店或慢洗店,在一定天数后得到干净的餐巾,对于干净的餐巾,可以新买(毕竟你第一天没餐巾,你只能买),也可以提前送到快慢洗店去洗,然后在用的那一天取回来
现在关系整清了,如何建图呢?一天有两个时间点,那我们将一天拆成两个点 \((i, i + n)\),前者为开始,后者为结束,如果拖着,那就将 \(i + n \rightarrow i + n + 1\) 连边,费用为零(毕竟不用洗),如果送到快洗店,要洗 \(t_1\) 天,那么将 \(i + n \rightarrow i + t_1\) 连边,费用为快洗店费用,慢洗店同理,慢洗店要洗 \(t_2\) 天,那么将 \(i + n \rightarrow i + t_2\) 连边,费用为慢洗店费用,以上边的流量都为 \(+ \infty\)(题目中没说洗衣店有数量限制),接下来,就是源点与汇点的关系了,源点代表的含义是提供餐巾(可能是干净的,也可能是脏的),汇点代表的含义是收集餐巾,所以,源点向结束点连边(源点向结束点提供脏餐巾),流量为这一天的餐巾数需要,开始点要向汇点连边(开始点向汇点提供脏餐巾),流量也为这一天的餐巾需要,这两个边费用都为 \(0\),同时原点还要向开始点连边,费用为 购买餐巾的费用(源点向开始点提供干净的餐巾源点区别对待),流量为 \(+ \infty\)
图联通了,跑最小费用最大流

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 2010;
const ll inf = 1e18;

int n, cnt = 1, S, T, t1, t2;
ll maxflow, cost, p, a, b;
int h[N << 1], d[N << 1];
ll r[N];
bool inque[N << 1];

struct edge {
	int v, nxt;
	ll w, f;
} e[300010];

inline void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
	e[cnt].w = w;
	e[cnt].f = f;
}

bool spfa() {
	for (int i = 0; i <= T; ++ i) {
		inque[i] = 0;
		d[i] = 1e9;
	}
	deque<int> q;
	q.push_back(S);
	inque[S] = 1;
	d[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		inque[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && d[v] > d[u] + e[i].f) {
				d[v] = d[u] + e[i].f;
				if (!inque[v]) {
					inque[v] = 1;
					if (!q.empty() && d[v] <= d[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
	}
	return (d[T] < 1e9);
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll rest = flow, rlow = 0;
	inque[u] = 1;
	for (int i = h[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (e[i].w <= 0 || d[v] != d[u] + e[i].f || inque[v])	continue;
		rlow = dfs(v, min(rest, e[i].w));
		if (rlow) {
			e[i].w -= rlow;
			e[i ^ 1].w += rlow;
			rest -= rlow;
			cost += e[i].f * rlow;
		}
	}
	return flow - rest;
}

int main() {
	n = read();
	int las = n * 2;
	T = las + 1;
	p = read(), t1 = read(), a = read(), t2 = read(), b = read();
	for (int i = 1; i <= n; ++ i) {
		r[i] = read();
		add(S, i + n, r[i], 0), add(i + n, S, 0, 0); // 源点连向结束点
		add(i, T, r[i], 0), add(T, i, 0, 0); // 开始点连向汇点
		add(S, i, r[i], p),	add(i, S, 0, -p);
		if (i + n + 1 <= las) { // 将脏餐巾留到明天晚上
			add(i + n, i + n + 1, inf, 0);
			add(i + n + 1, i + n, 0, 0);
		}
		if (i + t1 <= n) { // 送到快洗店
			add(i + n, i + t1, inf, a);
			add(i + t1, i + n, 0, -a);
		}
		if (i + t2 <= n) { // 送到慢洗店
			add(i + n, i + t2, inf, b);
			add(i + t2, i + n, 0, -b);
		}
	}
	while (spfa()) {
		for (int i = S; i <= T; ++ i) {
			inque[i] = 0;
		}
		while (dfs(S, inf)) {
			for (int i = S; i <= T; ++ i) {
				inque[i] = 0;
			}
		}
	}
	printf("%lld\n", cost);
	return 0;
}

10、「网络流 24 题」软件补丁

\(n\) 很小,似乎可以状压,最终要到达 \(0\) 状态(即没有错误)就是答案,根据补丁信息将一些状态之间连边,边权为补丁消耗的时间,跑最短路就行了(网络流?)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 22;
const int M = 105;

int n, m, s;
int t[M], b1[M], b2[M], f1[M], f2[M], dis[(1 << N) + 1];
char str[N];
int inque[(1 << N) + 1];
queue<int> q;

int main() {
	n = read(), m = read();
	s = (1ll << n) - 1;
	for (int i = 1; i <= m; ++ i) {
		t[i] = read();
		cin >> str;
		int siz = strlen(str);
		for (int j = 0; j < siz; ++ j) {
			if (str[j] == '+') {
				b1[i] |= (1 << j);
			}
			if (str[j] == '-') {
				b2[i] |= (1 << j);
			}
		}
		scanf("%s", str);
		siz = strlen(str);
		for (int j = 0; j < siz; ++ j) {
			if (str[j] == '-') {
				f1[i] |= (1 << j);
			}
			if (str[j] == '+') {
				f2[i] |= (1 << j);
			}
		}
	}
	for (int i = 0; i <= (1 << n); ++ i) {
		dis[i] = 1e9;
	}
	dis[s] = 0;
	q.push(s);
	inque[s] = 1;
	while (!q.empty()) {
		int u = q.front();
		for (int i = 1; i <= m; ++ i) {
			if (((u & b1[i]) == b1[i]) && ((u & b2[i]) == 0)) {
				int v = ((u | f1[i]) | f2[i]) ^ f1[i];
				// 这里先 | f1[i] 再 ^ f1[i],为了防止原本没有此错误,一异或有了
				if (dis[v] > dis[u] + t[i]) {
					dis[v] = dis[u] + t[i];
					if (!inque[v]) {
						q.push(v);
						inque[v] = 1;
					}
				}
			}
		}
		q.pop();
		inque[u] = 0;
	}
	if (dis[0] == 1e9) {
		printf("0\n");
	}
	else {
		printf("%d\n", dis[0]);
	}
	return 0;
}

11、「网络流 24 题」数字梯形

和那个最长递增子序列很想,处理三个问题,大体看一眼,第三题最简单,就是一个杨辉三角的递推 \(\text{DP}\),和网络流没关系
第二问,允许在数字节点处重合,边不能重复,那我们建边时将流量设为 \(1\) 即可,费用就是将杨辉三角的每个位置的权值放到边上
第一问,数字节点和边都不能重复,那就在问题二的基础上,将节点拆开,节点之间连边,流量为 \(1\),费用为 \(0\),同时两个节点一个连接从前面推到这的,一个负责连接从这个点出发到达的其他点

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 105;

int m, n, cnt = 1, cot, S, T;
ll maxflow, cost;
int tap[N][N], h[N * N], id[N][N], cur[N * N];
ll dis[N * N], dp[N][N];
bool inque[N * N];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N << 3];
queue<int> q;

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u];
	e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v];
	e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa() {
	while (!q.empty())	q.pop();
	for (int i = S; i <= T; ++ i) {
		dis[i] = -1e9;
		cur[i] = h[i];
	}
	q.push(S);
	inque[S] = 1;
	dis[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!inque[v]) {
					q.push(v);
					inque[v] = 1;
				}
			}
		}
		q.pop();
		inque[u] = 0;
	}
	return dis[T] > -1e9;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	inque[u] = 1;
	ll rest = flow, rlow = 0;
	for (int i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		cur[u] = i;
		if (!inque[v] && dis[v] == dis[u] + e[i].f && e[i].w) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				rest -= rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	inque[u] = 0;
	return flow - rest;
}

void work1() {
	T = cot * 2 + 1;
	for (int i = 1; i <= m; ++ i) {
		add(S, id[1][i], 1, 0);
	}
	for (int i = 1; i < n; ++ i) {
		for (int j = 1; j <= i + m - 1; ++ j) {
			add(id[i][j], id[i][j] + cot, 1, 0);
			add(id[i][j] + cot, id[i + 1][j], 1, tap[i][j]);
			add(id[i][j] + cot, id[i + 1][j + 1], 1, tap[i][j]);
		}
	}
	for (int i = 1; i <= n + m - 1; ++ i) {
		add(id[n][i], id[n][i] + cot, 1, 0);
		add(id[n][i] + cot, T, 1, tap[n][i]);
	}
	while (spfa()) {
		while (dfs(S, 1e9));
	}
	printf("%lld\n", cost);
}

void work2() {
	T = cot + 1;
	for (int i = 1; i <= m; ++ i) {
		add(S, id[1][i], 1, 0);
	}
	for (int i = 1; i < n; ++ i) {
		for (int j = 1; j <= m + i - 1; ++ j) {
			add(id[i][j], id[i + 1][j], 1, tap[i][j]);
			add(id[i][j], id[i + 1][j + 1], 1, tap[i][j]);
		}
	}
	for (int i = 1; i <= m + n - 1; ++ i) {
		add(id[n][i], T, 1e9, tap[n][i]);
	}
	while (spfa()) {
		while (dfs(S, 1e9));
	}
	printf("%lld\n", cost);
}

void work3() {
	for (int i = 1; i <= n + m - 1; ++ i) {
		dp[n][i] = tap[n][i];
	}
	for (int i = n - 1; i; -- i) {
		for (int j = 1; j <= i + m - 1; ++ j) {
			dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + tap[i][j];
		}
	}
	ll ans = 0;
	for (int i = 1; i <= m; ++ i) {
		ans += dp[1][i];
	}
	printf("%lld\n", ans);
}

void clr() {
	cnt = 1;
	cost = 0;
	memset(e, 0, sizeof e);
	memset(dis, 0, sizeof dis);
	memset(inque, 0, sizeof inque);
	memset(h, 0, sizeof h);
}

int main() {
	m = read(), n = read();
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m + i - 1; ++ j) {
			tap[i][j] = read();
			id[i][j] = ++ cot;
		}
	}
	work1();
	clr();
	work2();
	clr();
	work3();
	return 0;
}

12、「网络流 24 题」运输问题

很模板的题了,源点向仓库连边,流量为 \(a_i\),费用为 \(0\),商店向汇点连边,流量为 \(b_i\),费用为 \(0\),仓库和商店之间连边,费用为 \(c_{i, j}\),流量为 \(+ \infty\),跑最小费用最大流,然后还原边,跑最大费用最大流,还原边操作就是将反向边的流量返还给正向边

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 110;

int n, m, cnt = 1, S, T;
ll cost;
int cur[N << 1], h[N << 1];
ll a[N], b[N], c[N][N], dis[N << 1];
bool vis[N << 1];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N << 2];

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa1() {
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dis[i] = 1e9;
	}
	queue<int> q;
	q.push(S);
	dis[S] = 0;
	vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dis[v] > dis[u] + e[i].f) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
		q.pop();
		vis[u] = 0;
	}
	return dis[T] < 1e9;
}

int spfa2() {
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dis[i] = -1e9;
	}
	queue<int> q;
	q.push(S);
	dis[S] = 0;
	vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dis[v] < dis[u] + e[i].f) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
		q.pop();
		vis[u] = 0;
	}
	return dis[T] > -1e9;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int i = cur[u]; i && rest; i = e[i].nxt) {
		cur[u] = i;
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	m = read(), n = read();
	T = n + m + 1;
	for (int i = 1; i <= m; ++ i) {
		a[i] = read();
		add(S, i, a[i], 0); // 源点提供货物
	}
	for (int i = 1; i <= n; ++ i) {
		b[i] = read();
		add(i + m, T, b[i], 0); // 向汇点送货物
	}
	for (int i = 1; i <= m; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			c[i][j] = read();
			add(i, j + m, 1e18, c[i][j]);
		}
	}
	while (spfa1()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	cost = 0;
	for (int i = 2; i <= cnt; i += 2) {
		e[i].w += e[i ^ 1].w;
		e[i ^ 1].w = 0;
	}
	while (spfa2()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	return 0;
}

13、「网络流 24 题」分配问题

也是一道很模板的题甚至上一题的代码改改建边和输入就能过,源点向人连边,费用为 \(0\),流量为 \(1\),工作向汇点连边,费用为 \(0\),费用为 \(1\),人和工作之间连边,费用为 \(c_{i, j}\),流量为 \(1\),跑最小费用最大流和最大费用最大流即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 110;

int n, m, cnt = 1, S, T, cot;
ll cost;
int cur[N << 1], h[N << 1], id[N][N];
ll c[N][N], dis[N << 1];
bool vis[N << 1];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N << 2];

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa1() {
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dis[i] = 1e9;
	}
	queue<int> q;
	q.push(S);
	dis[S] = 0;
	vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dis[v] > dis[u] + e[i].f) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
		q.pop();
		vis[u] = 0;
	}
	return dis[T] < 1e9;
}

int spfa2() {
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dis[i] = -1e9;
	}
	queue<int> q;
	q.push(S);
	dis[S] = 0;
	vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dis[v] < dis[u] + e[i].f) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
		q.pop();
		vis[u] = 0;
	}
	return dis[T] > -1e9;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	n = read();
	T = n + n + n + 1;
	for (int i = 1; i <= n; ++ i) {
		add(S, i, 1, 0);
		add(i + n, T, 1, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			c[i][j] = read();
			add(i, j + n, 1, c[i][j]);
		}
	}
	while (spfa1()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	cost = 0;
	for (int i = 2; i <= cnt; i += 2) {
		e[i].w += e[i ^ 1].w;
		e[i ^ 1].w = 0;
	}
	while (spfa2()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	return 0;
}

14、「网络流 24 题」负载平衡

这个题要注意,每个仓库只能向相邻的两个仓库运送货物,第 \(n + 1\) 个仓库就是第 \(1\) 个仓库(环),同时最后要求每个仓库中的货物数量都相等,预先处理出平均值,如果一个仓库的货物量大于平均值,那我们就要将多的货物运出去,从仓库向汇点连一条边,流量为 \(a_i - aver\) (\(a_i\) 表示第 \(i\) 个仓库的货物数),如果货物量小于平均值,那就从别处运进货物,从源点向仓库连接一条边,流量为 \(aver - a_i\),相邻的仓库之间也要连边,流量为 \(+ \infty\),费用为 \(1\),跑最小费用最大流即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 110;
const ll inf = 1e18;

int n, S, T, cnt = 1;
ll cost, sum;
int h[N], cur[N];
ll dis[N], a[N];
bool vis[N];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N + N * 3];

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

ll spfa() {
	for (int i = S; i <= T; ++ i) {
		dis[i] = 1e18;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	vis[S] = 1, dis[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] > dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] <= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
	}
	return dis[T] < 1e18;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && !vis[v] && e[i].w) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	n = read();
	T = n + 1;
	for (int i = 1; i <= n; ++ i) {
		a[i] = read();
		sum += a[i];
		(i == 1) ? add(i, n, inf, 1) : add(i, i - 1, inf, 1);
		(i == n) ? add(i, 1, inf, 1) : add(i, i + 1, inf, 1);
	}
	sum /= n;
	for (int i = 1; i <= n; ++ i) {
		if (a[i] > sum) {
			add(i, T, a[i] - sum, 0);
		}
		if (a[i] < sum) {
			add(S, i, sum - a[i], 0);
		}
	}
	while (spfa()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	return 0;
}

15、「网络流 24 题」最长 k 可重区间集

题目大意,找一些线段,保证数轴上每个点被覆盖不超过 \(k\) 次,我们从线段入手,线段会有重叠和不重叠两种情况,如果没有重叠的线段,我们放心大胆的选就行,不用担心有的点会被覆盖超过 \(k\) 次,对于重叠的线段,我们就不能不过脑子直接选了
根据上面的分析建图,对于不重叠的边,我们连一条边,流量为 \(1\),费用为 \(0\),对于重叠的边,不能直接选,那我们就不连边,每个区间只能选一次,所以将区间拆成两个点,两个点之间连边,流量为 \(1\),费用为这个区间的长度,同时,我们要从源点流入 \(k\) 的流量,这样可以保证每个点不会被覆盖超过 \(k\) 次(不好理解可以这么想,假设所有的区间都相等,即所有区间都重叠,那我们最多只能选 \(k\) 个区间,所以流量为 \(k\)),但如何才能保证只有 \(k\) 的流量会流入这个网络流系统?再建一个源点 \(\_S\),将源点 \(S\)\(\_S\) 连一条流量为 \(k\) 的边,这样就 \(OK\) 了,\(\_S\) 向每一个区间连一条流量为 \(1\),费用为 \(0\) 的边,同时每一个区间在向汇点连一条流量为 \(1\),费用为 \(0\) 的边,建完图后跑最大费用最大流

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 510;
const ll inf = 1e18;

int n, k, cnt = 1, S, T, _S;
ll cost;
int h[N << 1], cur[N << 1];
ll dis[N << 1];
bool vis[N << 1];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N << 2];

struct mat {
	int l, r, len;
	bool operator < (const mat &b) {
		return (l == b.l) ? r < b.r : l < b.l;
	}
} m[N];

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

bool spfa() {
	for (int i = S; i <= T; ++ i) {
		dis[i] = -1e9;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	vis[S] = 1;
	dis[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] >= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
		vis[u] = 0;
	}
	return dis[T] > -1e9;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rlow = 0, rest = flow;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += e[i].f * rlow;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	n = read(), k = read();
	_S = 1, T = n * 2 + 2;
	add(S, _S, k, 0);
	for (int i = 1; i <= n; ++ i) {
		m[i].l = read(), m[i].r = read();
		if (m[i].l > m[i].r)	swap(m[i].l, m[i].r);
	}
	sort(m + 1, m + n + 1);
	for (int i = 1; i <= n; ++ i) {
		add(i + 1, i + n + 1, 1, (m[i].len = m[i].r - m[i].l));
		add(i + n + 1, T, 1, 0);
		add(_S, i + 1, 1, 0);
	}
	for (int i = 1; i < n; ++ i) {
		for (int j = i + 1; j <= n; ++ j) {
			if (m[i].l + m[i].len <= m[j].l) {
				add(i + n + 1, j + 1, 1, 0);
			}
		}
	}
	while (spfa()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	return 0;
}

16、「网络流 24 题」星际转移

这道题有点分层图的意思了,先分析题目,对于人来说,可以选择暂时留在一个太空站上,也可以选择乘坐太空船去别的地方,最后的目的地是月球
按照天数分层,对于第 \(i\) 天的第 \(j\) 个太空站可以直接连向第 \(i + 1\) 天的第 \(j\) 个太空站,也可以根据飞船的路程连向第 \(i + 1\) 天的飞船能到达的下一个太空站,将每一天的月球的位置向汇点连一条边,流量为 \(+ \infty\),源点点向第 \(0\) 天的地球连边,流量为地球的人数,跑最大流,每天都跑一次最大流,知道最大流超过 \(k\),代表人已经全到月球上了
判断是否有解就是判断图是否连通,并查集即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define get(x, y)	(x + (y * (n + 2)))

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 15;
const int M = 25;
const int K = 55;
const ll inf = 1e18;

int n, m, k, S, T, cnt = 1;
ll maxflow;
int num[M], r[M], s[M][N], fa[N];
int dep[N * 10000], cur[N * 10000], h[N * 10000];
bool vis[N * 10000];

struct edge {
	int v, nxt;
	ll w;
} e[N * N * 10005];

int find(int x) {
	return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}

inline void Union(int x, int y) {
	int fx = find(x), fy = find(y);
	if (fx != fy)	fa[fx] = fy;
}

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0;
}

bool bfs() {
	for (int i = 0; i <= T; ++ i) {
		dep[i] = 1e9;
		vis[i] = 0;
	}
	queue<int> q;
	q.push(S);
	dep[S] = 0;
	cur[S] = h[S];
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && dep[v] > dep[u] + 1) {
				dep[v] = dep[u] + 1;
				cur[v] = h[v];
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
				if (v == T)	return 1;
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T || flow == 0) {
		maxflow += flow;
		return flow;
	}
	ll rlow = 0, rest = flow;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
			}
			else	dep[v] = 1e9;
		}
	}
	return flow - rest;
}

int main() {
	n = read(), m = read(), k = read();
	T = N * 10000 - 1;
	S = N * 10000 - 2;
	for (int i = 0; i <= n + 1; ++ i)	fa[i] = i;
	for (int i = 1; i <= m; ++ i) {
		num[i] = read(), r[i] = read();
		for (int j = 0; j < r[i]; ++ j) {
			s[i][j] = read();
			if (s[i][j] == -1)	s[i][j] = n + 1;
			if (j)	Union(s[i][j - 1], s[i][j]);
		}
	}
	if (find(0) != find(n + 1)) {
		puts("0");
		return 0;
	}
	add(S, get(0, 0), k);
	add(get(n + 1, 0), T, inf);
	int day = 1;
	while (day) {
		add(get(n + 1, day), T, inf);
		for (int i = 0; i <= n + 1; ++ i) {
			add(get(i, (day - 1)), get(i, day), inf);
		}
		for (int i = 1; i <= m; ++ i) {
			int people = num[i], xh = r[i];
			int a = s[i][(day - 1) % xh], b = s[i][day % xh];
			add(get(a, (day - 1)), get(b, day), people);
		}
		while (bfs()) {
			dfs(S, 1e18);
		}
		if (maxflow >= k)	break;
		++ day;
	}
	printf("%d\n", day);
	return 0;
}

17、「网络流 24 题」孤岛营救

分层图,钥匙的种类很少,可以状压,我们就按照钥匙的状态分层,钥匙的状态:这一层有什么种类的钥匙(对于每种钥匙来说就是这一层是否有这种钥匙),跑分层图最短路
分层图最短路可以看一下这篇博客分层图最短路
代码中有注释

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define get(x, y) (y * sum + x)

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 20;

int n, m, p, k, cot, s, cnt, sum, tot, cs;
int id[N][N], fg[110][110], key[15][15], num[20], h[1000010];
ll dis[10000010];
bool vis[10000010], havekey[20];

struct edge {
	int v, nxt;
	ll w;
} e[10000010];

void input() {
	n = read(), m = read(), p = read(), k = read();
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			id[i][j] = ++ cot; // 对点进行标号
		}
	}
	for (int i = 1; i <= k; ++ i) {
		int x = read(), y = read(), a, b;
		a = id[x][y];
		x = read(), y = read();
		b = id[x][y];
		x = read();
		if (x == 0)	x = -1;
		fg[a][b] = fg[b][a] = x; // a到b是否通行
	}
	s = read();
	for (int i = 1; i <= s; ++ i) {
		int x = read(), y = read(), q = read();
		++ num[q];
		key[q][num[q]] = id[x][y]; // 记录位置
	}
}

void add(int u, int v, ll w) {
	e[++ cnt].v = v;
	e[cnt].nxt = h[u];
	e[h[u] = cnt].w = w;
}

void build() {
	sum = n * m, cs = (1 << p), tot = sum * cs;
	// sum 每一层的节点数
	// cs  层数
	// tot 总的节点数
	for (int c = 0; c < cs; ++ c) {
		for (int i = 1; i <= p; ++ i) {
			if (c & (1 << (i - 1))) { // 是否有第i种钥匙
				havekey[i] = 1;
			}
			else {
				havekey[i] = 0;
			}
		}
		for (int i = 1; i <= n; ++ i) {
			for (int j = 1; j <= m; ++ j) {
				int now = id[i][j], you = id[i][j + 1], xia = id[i + 1][j];
				// 向右边的点和下边的点连边
				if (you && ~fg[now][you]) { // 该点存在且没有墙
					if (!fg[now][you] || havekey[fg[now][you]]) { 
						// 没有门或者有这个门的钥匙
						add(get(now, c), get(you, c), 1);
						add(get(you, c), get(now, c), 1); // 建立双向边
					}
				}
				if (xia && ~fg[now][xia]) {
					if (!fg[now][xia] || havekey[fg[now][xia]]) {
						add(get(now, c), get(xia, c), 1);
						add(get(xia, c), get(now, c), 1);
					}
				}
			}
		}
		for (int i = 1; i <= p; ++ i) {
			if (!havekey[i]) { // 没有第i种钥匙
				int t = c + (1 << (i - 1)); // 有第i种钥匙的层
				for (int j = 1; j <= num[i]; ++ j) {
					int x = key[i][j]; // 第i种钥匙的位置
					add(get(x, c), get(x, t), 0);
					// 到达这个点,拿到钥匙,就进入有该钥匙的层了
				}
			}
		}
	}
}

void spfa() {
	for (int i = 1; i <= tot; ++ i) {
		dis[i] = 1e9;
	}
	queue<int> q;
	q.push(id[1][1]);
	dis[id[1][1]] = 0;
	vis[id[1][1]] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] > dis[u] + e[i].w) {
				dis[v] = dis[u] + e[i].w;
				if (!vis[v]) {
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
}

int main() {
	input();
	build();
	spfa();
	int T = id[n][m];
	ll ans = 1e9;
	for (int i = 0; i < cs; ++ i) {
		ans = min(ans, dis[get(T, i)]);
	}
	if (ans < 1e9) {
		printf("%lld\n", ans);
	}
	else {
		puts("-1");
	}
	return 0;
}

18、「网络流 24 题」航空路线

把题目改成人话就是从起点找两条不相交的能到终点的最长的路径,然后输出,除了起点和终点,其他点只能经过一次,只能经过一次,拆点连边流量为 \(1\) 呀,因为还要记录经过了几个点,所以拆的点之间费用为 \(1\),起点和终点也只能经过 \(2\) 次(毕竟只招 \(2\) 条路经),所以拆点,点之间连流量为 \(2\),费用为 \(1\) 的边?这里我们要想一个问题,如果我们这么建边,那到时候,起点和终点就多记了一次,即最后的最大流中多了 \(2\),减去就行了,这里还有一种解决方法,起点拆成的点之间连两条边,流量都为 \(1\),但一个有费用,一个没有费用,这样,就既保证了总流量为 \(2\),也保证了记录点数不会多记,终点也是一样的
然后根据飞机的直航路线连边,由于城市名都是字符串,所以用 map 来标号

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("IOI AK ME!")

const int N = 110;

int n, m, cnt = 1, cot, S, T;
ll cost, maxflow;
int h[N << 1], cur[N << 1];
ll dis[N << 1];
bool vis[N << 1];
unordered_map<string, int> mp;
unordered_map<int, string> mp2;
vector<int> d1, d2;

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N << 1];

inline void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa() {
	for (int i = S; i <= T; ++ i) {
		cur[i] = h[i];
		dis[i] = -1e9;
	}
	deque<int> q;
	q.push_back(S);
	dis[S] = 0;
	vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] >= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
		vis[u] = 0;
	}
	return dis[T] > -1e9;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

void dfs1(int u) {
	cout << mp2[u - n] << endl;
	vis[u] = 1;
	for (int i = h[u]; i; i = e[i].nxt) {
		if (!e[i].w) {
			int v = e[i].v;
			if (v <= n) {
				dfs1(v + n);
			}
			break;
		}
	}
}

void dfs2(int u) {
	for (int i = h[u]; i; i = e[i].nxt) {
		if (!e[i].w) {
			int v = e[i].v;
			if (vis[v + n])	continue;
			if (v <= n) {
				dfs2(v + n);
			}
		}
	}
	cout << mp2[u - n] << endl;
}

int main() {
	int fg = 0;
	scanf("%d%d", &n, &m);
	T = n * 2 + 1;
	add(S, 1, 2, 0);
	add(2 * n, T, 2, 0);
	for (int i = 1; i <= n; ++ i) {
		string s;
		cin >> s;
		mp[s] = ++ cot;
		mp2[cot] = s;
		if (i != 1 && i != n)	add(i, i + n, 1, 1);
		else {
			add(i, i + n, 1, 1);
			add(i, i + n, 1, 0);
		}
	}
	for (int i = 1, x, y; i <= m; ++ i) {
		string a, b;
		cin >> a >> b;
		x = mp[a], y = mp[b];
		if (x > y)	swap(x, y);
		add(x + n, y, 1, 0);
		if (x == 1 && y == n)	fg = 1;
	}
	while (spfa()) {
		while(dfs(S, 1e18));
	}
	if (maxflow == 2) {
		printf("%lld\n", cost);
	}
	else {
		if (maxflow == 1 && fg) {
			puts("2");
			cout << mp2[1] << '\n' << mp2[n] << '\n' << mp2[1] << endl;
		}
		else {
			puts("No Solution!");
		}
		return 0;
	}
	dfs1(1 + n);
	dfs2(1 + n);
	return 0;
}

19、「网络流 24 题」汽车加油行驶问题

又是一道分层图
直接说建图,我们按照已经使用的汽油量来分层,所以只会分 \(k\) 层,第 \(0\) 层则代表油箱是满的,第 \(k\) 层代表油箱已经空了
对于每一层之间的关系,如果经过了一个加油站,不管有还剩多少,都向第 \(0\) 层的这个点连边(因为加满了,而且是强制加油),如果这个位置不是加油站同时还有油,那就向下一层(如果当前是第 \(i\) 层,那下一层就是第 \(i + 1\) 层)的点连边,四个方向四个点,向上或向左走就有费用 \(b\),否则费用为 \(0\),那流量为多少呢?流量为 \(1\),因为只有一辆车,而且根据贪心思想,我们不会回到原来的点,所以设为 \(1\),如果油空了且没有加油站,那就建一个,连一条边到第 \(0\) 层的该点,费用为 \(a + c\),流量为 \(1\)
这里有个坑,如果到达终点时,油刚好用完,那就不用再建加油站了,题目中也说了,起点和终点不设加油站

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define get(x, y) (x + (y * n * n))
inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 110;

int n, k, a, b, c, S, T, cot, cnt = 1;
ll cost;
int g[N][N], id[N][N], h[10000010], cur[10000010];
ll dis[10000010];
bool vis[10000010];

struct edge {
	int v, nxt;
	ll w, f;
} e[20000010];

inline void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[(h[u] = cnt)].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[(h[v] = cnt)].w = 0, e[cnt].f = -f;
}

ll spfa() {
	for (int i = 0; i <= T; ++ i) {
		dis[i] = 1e9;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	vis[S] = 1, dis[S] = 0;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] > dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] <= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
	}
	return dis[T] < 1e9;
}


ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

// 按耗费的油量分层

int main() {
	n = read(), k = read(), a = read(), b = read(), c = read();
	T = n * n * (k + 1) + 1;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			id[i][j] = ++ cot; // 编号
		}
	}
	add(S, get(id[1][1], 0), 1, 0); // 源点向起始点连边
	for (int i = 1; i <= k; ++ i) { // 向汇点连边
		add(get(id[n][n], i), T, 1, 0);
	}
	for (int i = 1; i <= n; ++ i) { // 枚举每一个点
		for (int j = 1; j <= n; ++ j) {
			g[i][j] = read(); // 输入油站情况
			if (g[i][j]) { // 有加油站,先加油
				for (int l = 1; l <= k; ++ l) { // 不管油还剩多少,先加上
					add(get(id[i][j], l), get(id[i][j], 0), 1, a);
				}
				// 下一步的运动
				if (i < n)	add(get(id[i][j], 0), get(id[i + 1][j], 1), 1, 0);
				if (j < n)	add(get(id[i][j], 0), get(id[i][j + 1], 1), 1, 0);
				if (i > 1)	add(get(id[i][j], 0), get(id[i - 1][j], 1), 1, b);
				if (j > 1)	add(get(id[i][j], 0), get(id[i][j - 1], 1), 1, b);
			}
			else { // 没有加油站
				for (int l = 0; l < k; ++ l) { // 耗油量+1
					if (i < n)	add(get(id[i][j], l), get(id[i + 1][j], (l + 1)), 1, 0);
					if (j < n)	add(get(id[i][j], l), get(id[i][j + 1], (l + 1)), 1, 0);
					if (i > 1)	add(get(id[i][j], l), get(id[i - 1][j], (l + 1)), 1, b);
					if (j > 1)	add(get(id[i][j], l), get(id[i][j - 1], (l + 1)), 1, b);
				}
				add(get(id[i][j], k), get(id[i][j], 0), 1, a + c);// 没有加油站,自己建一个
			}
		}
	}
	while (spfa()) {
		while (dfs(S, 1e18));
	}
	printf("%lld\n", cost);
	return 0;
}

20、「网络流 24 题」深海机器人

这道题,出发点有多个,结束点也有多个,每条路径上都有一个费用,但这个费用用完就没了(一次性),我们可以利用上面的建边方式,每条边分成两种建,一种流量为 \(1\),费用为 \(1\),一种流量为 \(+ \infty\),费用为 \(0\),这样可以保证每条边的费用只会用一次,这样可以保证每条边的费用会被第一个经过的机器人带走吗?根据 \(\text{SPFA}\) 最长路的判断,很明显,走有费用的边更优,所以可以保证一定先走有费用的边,剩下的建图就很简单了,自己能想出来

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug cout << "IOI AK ME" << endl;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 20;
const ll inf = 1e11;

int a, b, n, m, cnt = 1, cot, T, S;
ll cost;
int h[N * N], cur[N * N], id[N][N];
ll dis[N * N];
bool vis[N * N];

struct edge {
	int v, nxt;
	ll w, f;
} e[20000010];

inline void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa() {
	for (int i = S; i <= T; ++ i) {
		dis[i] = -inf;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	dis[S] = 0, vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] >= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
		vis[u] = 0;
	}
	return dis[T] > -inf;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (!vis[v] && dis[v] == dis[u] + e[i].f && e[i].w) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				rest -= rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				cost += rlow * e[i].f;
//				e[i].f = 0;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	a = read(), b = read();
	n = read(), m = read();
	T = (n + 1) * (m + 1) + 1;
	for (int i = 0; i <= n; ++ i) {
		for (int j = 0; j <= m; ++ j) {
			id[i][j] = ++ cot;
		}
	}
	for (int i = 0; i <= n; ++ i) {
		for (int j = 0; j < m; ++ j) {
			ll x = read();
			add(id[i][j], id[i][j + 1], 1, x);
			add(id[i][j], id[i][j + 1], inf, 0);
		}
	}
	for (int i = 0; i <= m; ++ i) {
		for (int j = 0; j < n; ++ j) {
			ll x = read();
			add(id[j][i], id[j + 1][i], 1, x);
			add(id[j][i], id[j + 1][i], inf, 0);
		}
	}
	for (int i = 1; i <= a; ++ i) {
		ll z = read();
		int x = read(), y = read();
		add(S, id[x][y], z, 0);
	}
	for (int i = 1; i <= b; ++ i) {
		ll z = read();
		int x = read(), y = read();
		add(id[x][y], T, z, 0);
	}
	while (spfa()) {
		while (dfs(S, inf));
	}
	printf("%lld\n", cost);
	return 0;
}

21、「网络流 24 题」火星探险

和上一个题大部分都是一样的,只是有些小变化,第一,有些点不能连边,这个判断一下就行了,第二,上一个题是边权,这个题是点权,这个也很简单,插点操作就可以把点权转化为边权,至于拆开的点之间的建边,还是建两种,和上题一样,而对于每个格子之间的建边,流量为 \(+ \infty\),费用为 \(0\),源点向出发点连边,结束点向汇点连边,最后要输出行走方案,所以对边,我们还要再设一个计数的值,如果这个值等于它的反向边的流量,那这个边就不能走了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug cout << "IOI AK ME" << endl;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 50;
const ll inf = 1e11;

int a, n, m, cnt = 1, cot, T, S;
ll cost, maxflow;
int h[N * N * 2], cur[N * N * 2], id[N][N], g[N][N];
ll dis[N * N * 2];
bool vis[N * N * 2];

struct edge {
	int v, nxt;
	ll w, f, jl;
} e[20000010];

inline void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f, e[cnt].jl = 0;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f, e[cnt].jl = 0;
}

int spfa() {
	for (int i = S; i <= T; ++ i) {
		dis[i] = -inf;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	dis[S] = 0, vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] >= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
		vis[u] = 0;
	}
	return dis[T] > -inf;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	vis[u] = 1;
	ll rest = flow, rlow = 0;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (!vis[v] && dis[v] == dis[u] + e[i].f && e[i].w) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				rest -= rlow;
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				cost += rlow * e[i].f;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

void dfs1(int u, int c) {
	for (int i = h[u]; i; i = e[i].nxt) {
		if (e[i ^ 1].w != e[i].jl) {
			++ e[i].jl;
			int v = e[i].v;
			if (v != T) {
				if (v + cot == u + 1) {
					printf("%d 1\n", c);
				}
				else {
					printf("%d 0\n", c);
				}	
			}
			dfs1(v + cot, c);
			break;
		}
	}
}

int main() {
	a = read(),	m = read(), n = read();
	T = n * m * 2 + 1;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			id[i][j] = ++ cot;
			g[i][j] = read();
		}
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			if (g[i][j] == 1)	continue;
			if (g[i][j] == 2) {
				add(id[i][j], id[i][j] + cot, 1, 1);
			}
			add(id[i][j], id[i][j] + cot, inf, 0);
			if (i < n && g[i + 1][j] != 1) {
				add(id[i][j] + cot, id[i + 1][j], inf, 0);
			}
			if (j < m && g[i][j + 1] != 1) {
				add(id[i][j] + cot, id[i][j + 1], inf, 0);
			}
		}
	}
	add(S, id[1][1], a, 0);
	add(id[n][m] + cot, T, a, 0);
	while (spfa()) {
		while (dfs(S, inf));
	}
	for (int i = 1; i <= maxflow; ++ i) {
		dfs1(id[1][1] + cot, i);
	}
	return 0;
}

22、「网络流 24 题」骑士共存

这道题和上面的方格取数算是同一类型的,做法也差不多,观察题目给的图发现还是可以分成奇数和偶数两类点,源点向奇数类点连边,偶数类点向汇点连边,奇数类点和偶数类点之间根据条件连边,跑最大流最小割即可
连边和统计答案的时候,别忘了有些格子不能放骑士!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 210;
const ll inf = 1e11;
const int fx[8] = {1, 2, 2, 1, -1, -2, -2, -1};
const int fy[8] = {2, 1, -1, -2, -2, -1, 1, 2};

int n, m, cot, cnt = 1, T, S;
ll maxflow;
int id[N][N], h[N * N], cur[N * N], dep[N * N];
int g[N][N], vis[N * N];

struct edge {
	int v, nxt;
	ll w;
} e[N * N << 4];

inline void add(int u, int v, ll w) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0;
}

int bfs() {
	for (int i = S; i <= T; ++ i) {
		dep[i] = 1e9;
		cur[i] = h[i];
		vis[i] = 0;
	}
	queue<int> q;
	q.push(S);
	dep[S] = 0, vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop(), vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dep[v] > dep[u] + 1 && e[i].w) {
				dep[v] = dep[u] + 1;
				if (v == T)	return 1;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	return 0;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		maxflow += flow;
		return flow;
	}
	ll rlow = 0, rest = flow;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dep[v] == dep[u] + 1 && e[i].w) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
			}
		}
	}
	return flow - rest;
}

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			id[i][j] = ++ cot;
		}
	}
	for (int i = 1; i <= m; ++ i) {
		int x = read(), y = read();
		g[x][y] = 1;
	}
	T = n * n + 1;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= n; ++ j) {
			if (g[i][j])	continue;
			if ((i + j) & 1) {
				add(S, id[i][j], 1);
				for (int k = 0; k < 8; ++ k) {
					int x = i + fx[k], y = j + fy[k];
					if (!g[x][y] && x >= 1 && x <= n && y >= 1 && y <= n) {
						add(id[i][j], id[x][y], inf);
					}
				}
			}
			else {
				add(id[i][j], T, 1);
			}
		}
	}
	while (bfs()) {
		dfs(S, inf);
	}
	printf("%lld\n", n * n - m - maxflow);
	return 0;
}

23、「网络流 24 题」最长 k 可重线段集

这道题与前面的最长k可重区间集很想,但这题的坑更多,我们可以将线段的左右端点映射到数轴上,但是,有一种很特殊的情况,如果我们只是单纯的把左右端点映射到数轴上,那么,对于与 \(x\) 轴垂直的线段,如果就这么映射上去,那这条线段都不存在!怎么办呢,这里有一个做法——扩域,就是将所有线段左右端点都乘上 \(2\) 再映射,如果左右端点相同,那右端点向右移一位(毕竟是开区间),即 \((p_1, p_2)\) 变成 \((p_1 \times 2, p_2 \times 2 + 1)\),但是这样也有一个问题,不扩域前映射到数轴上的不重叠的线段,可能扩域后就重叠了,如 \((p_1, p_2), (p_3, p_4), p_3 = p_2\),一扩域后,就变成了 \((2p_1, 2p_2 + 1), (2p_3, 2p_4)\),这两条线段就有重叠部分了,这怎么办呢?我们把 \(2p_3\) 也加上 \(1\),就行了
剩下的就跟那道题差不多了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("IOI AK ME!");

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 510;
const ll inf = 1e18;

int n, k, cnt = 1, T, S, _S;
ll cost;
int h[N << 1], cur[N << 1];
ll dis[N << 1];
bool vis[N << 1];

struct stg {
	int xl, yl, xr, yr;
	ll len;
	int operator < (const stg &b) {
		return xl == b.xl ? xr < b.xr : xl < b.xl;
	}
} s[510];

struct edge {
	int v, nxt;
	ll w, f;
} e[N * N + 2 * N + 10];

void add(int u, int v, ll w, ll f) {
	e[++ cnt].v = v, e[cnt].nxt = h[u], e[h[u] = cnt].w = w, e[cnt].f = f;
	e[++ cnt].v = u, e[cnt].nxt = h[v], e[h[v] = cnt].w = 0, e[cnt].f = -f;
}

int spfa() {
	for (int i = S; i <= T; ++ i) {
		dis[i] = -inf;
		cur[i] = h[i];
	}
	deque<int> q;
	q.push_back(S);
	dis[S] = 0, vis[S] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop_front();
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] < dis[u] + e[i].f && e[i].w) {
				dis[v] = dis[u] + e[i].f;
				if (!vis[v]) {
					vis[v] = 1;
					if (!q.empty() && dis[v] >= dis[q.front()]) {
						q.push_front(v);
					}
					else {
						q.push_back(v);
					}
				}
			}
		}
		vis[u] = 0;
	}
	return dis[T] > -inf;
}

ll dfs(int u, ll flow) {
	if (u == T) {
		return flow;
	}
	vis[u] = 1;
	ll rlow = 0, rest = flow;
	for (int &i = cur[u]; i && rest; i = e[i].nxt) {
		int v = e[i].v;
		if (dis[v] == dis[u] + e[i].f && e[i].w && !vis[v]) {
			if ((rlow = dfs(v, min(rest, e[i].w)))) {
				e[i].w -= rlow;
				e[i ^ 1].w += rlow;
				rest -= rlow;
				cost += e[i].f * rlow;
			}
		}
	}
	vis[u] = 0;
	return flow - rest;
}

int main() {
	n = read(), k = read();
	_S = 1;
	T = n * 2 + 2;
	for (int i = 1; i <= n; ++ i) {
		s[i].xl = read(), s[i].yl = read(), s[i].xr = read(), s[i].yr = read();
		s[i].len = sqrt(1ll * (s[i].xr - s[i].xl) * (s[i].xr - s[i].xl) + 
			1ll * (s[i].yr - s[i].yl) * (s[i].yr - s[i].yl));
		if (s[i].xr < s[i].xl)	swap(s[i].xl, s[i].xr);
		s[i].xr <<= 1, s[i].xl <<= 1;
		(s[i].xr == s[i].xl) ? (s[i].xr |= 1) : (s[i].xl |= 1);
	}
	sort(s + 1, s + n + 1);
	add(S, _S, k, 0);
	for (int i = 1; i <= n; ++ i) {
		add(i + 1, i + n + 1, 1, s[i].len);
		add(i + n + 1, T, 1, 0);
		add(_S, i + 1, 1, 0);
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = n; j >= 1; -- j) {
			if (i == j)	continue;
			if (s[i].xr <= s[j].xl) {
				add(i + n + 1, j + 1, 1, 0);
			}
			else {
				break;
			}
		}
	}
	while (spfa()) {
		while (dfs(S, inf));
	}
	printf("%lld\n", cost);
	return 0;
}

网络流做法总结

做完这 \(23\) 道题后,算是对建模和方法一个小的总结

拆点法

将一个点拆成两个点,可以将点权转化为边权,也可以解决像一个点只能经过一次这样的问题,对于一些题目,则是可以转化为二分图,求最小割、最大匹配等

拆边法

自己起的名字,顾名思义,就是将一条边拆成多条边,可以应对如“一条边有一个费用,但这个费用只能用一次”这样的问题,解法就是拆成两条边,一条有费用,但流量为 \(1\),一条无费用,但流量为 \(+ \infty\),这个方法可以与上面的拆点法点权化边权组合来解题

分类法

也是自己起的名,像方格取数这样对格图操作的题,试试把格子分成两类,或者叫两个点集,一般可以转化成二分图,用来求最大匹配或最小割

分层图

根据时间、状态等来分层,每一层的图基本一样,图中有些要根据题目和自己的分层依据做修改,根据题目给的信息来连接不同的层,使图联通

降维法

降维打击 最后一个题就是将二维图转化为了一维图,还有一些小方法,像扩域等


网络流建边时,考虑好源点与汇点的关系,每条边的流量、费用要根据题目来设置,对于点的一些限制或边的一些限制,可以试试拆点或拆边。

posted @ 2023-01-18 09:56  yi_fan0305  阅读(53)  评论(0编辑  收藏  举报