网络流 24 题。

<1> 飞行员配对方案问题

妥妥 二分图匹配,外籍飞行员与英国飞行员分别在两侧,边的流量均为 1

Dinic 好些,时间复杂度 O(mn) ,匈牙利算法本质上就是 EK每次只找一条增广路,所以时间复杂度 O(mn) ,自然要慢一些。

// 飞行员配对方案问题
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 2000010;
int e[M], ne[M], h[N], w[M], idx;
int d[N], cur[N];
int m, n, S = 200001, T = 200002;
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
    e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx++;
}
bool bfs() {
    queue<int> q;
    q.push(S);
    memset(d, -1, sizeof d);
    cur[S] = h[S], d[S] = 0;
    while (q.size()) {
        int u = q.front();
        q.pop();
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (d[j] == -1 && w[i]) {
                d[j] = d[u] + 1;
                cur[j] = h[j];
                if (j == T)
                    return true;
                q.push(j);
            }
        }
    }
    return false;
}
int find(int u, int limit) {
    if (u == T)
        return limit;
    int flow = 0;
    for (int i = cur[u]; i != -1; i = ne[i]) {
        cur[u] = i;
        int j = e[i];
        if (d[j] == d[u] + 1 && w[i]) {
            int t = find(j, min(limit - flow, w[i]));
            if (!t)
                d[j] = -1;
            else
                w[i] -= t, w[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}
int dinic() {
    int re = 0, flow = 0;
    while (bfs())
        while (flow = find(S, 1e9)) re += flow;
    return re;
}
int main() {
    memset(h, -1, sizeof h);
    cin >> m >> n;
    int x, y;
    // 建图
    while (1) {
        cin >> x >> y;
        if (x == -1 && y == -1)
            break;
        add(x, y, 1);
    }
    for (int i = 1; i <= m; i++) add(S, i, 1);
    for (int i = m + 1; i <= n; i++) add(i, T, 1);
    // 输出答案
    printf("%d\n", dinic());
    // 处理配对方案
    for (int i = 0; i < idx; i++)
        if (e[i ^ 1] <= m && e[i] > m && e[i] <= n && !w[i])
            cout << e[i ^ 1] << ' ' << e[i] << '\n';
    return 0;
}

<2> 圆桌问题

可以把该题目想象成 二分图带权匹配

二分图左侧每一个节点代表 一个单位 ,右侧每个节点代表 一张圆桌

由于每个桌子出现的代表单位不可重复,所以每个单位各向每张圆桌连一条 容量为 1 的边。

每个单位连向源点容量都为 该单位人数 ,每张餐桌连向汇点容量都为 该餐桌可容纳人数

使用 dinic 求最大流即可。

// 圆桌问题
#include <bits/stdc++.h>
using namespace std;
const int N = 430, M = 150010;
int S = 429, T = 428;
int h[N], e[M], ne[M], w[M], idx;
int d[N], cur[N];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
    e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
}
bool bfs() {
    queue<int> q;
    q.push(S);
    memset(d, -1, sizeof d);
    cur[S] = h[S], d[S] = 0;
    while (q.size()) {
        int u = q.front();q.pop();
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (d[j] == -1 && w[i]) {
                d[j] = d[u] + 1;
                cur[j] = h[j];
                if (j == T) return true;
                q.push(j);
            }
        }
    }
    return false;
}
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
        cur[u] = i;
        int j = e[i];
        if (d[j] == d[u] + 1 && w[i]) {
            int t = find(j, min(limit - flow, w[i]));
            if (!t) d[j] = -1;
            else w[i] -= t, w[i ^ 1]  += t, flow += t;
        }
    }
    return flow;
}
int dinic() {
    int re = 0, flow = 0;
    while (bfs()) while (flow = find(S, 1e9)) re += flow;
    return re;
}
int main() {
    memset(h, -1, sizeof h);
    int n, m, sum = 0;
    cin >> m >> n;
    // 建图 
    for (int i = 1; i <= m; i ++ ) {
        int x;
        cin >> x;sum += x;
        add(S, i, x);
    }
    for (int i = 1; i <= n; i ++ ) {
        int x;
        cin >> x;
        add(i + m, T, x);
    }
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= n; j ++ )
            add(i, j + m, 1);
    int T = dinic();
    if (T != sum) cout << 0;
    else {
        cout << 1 << '\n';
        for (int i = 1; i <= m; i ++ ) {
            for (int j = h[i]; j != -1; j = ne[j]) {
                int u = e[j];
                if (u > m && u <= m + n && !w[j]) cout << u - m << ' ';
            }
            cout << '\n';
        }
    }
    return 0;
}

<3> 试题库问题

同样是 二分图带权匹配 ,二分图左侧表示每一道题目,二分图右侧表示每一类题目,试题需要的每种的类型的数量即为连向超级汇点的容量,每一道题目连向虚拟源点的容量就为 1 。最后只需要判断最大流是否等于 m 即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 10010;
int w[M], e[M], ne[M], h[N * 2], idx;
int cur[N * 2], d[N * 2];
int S = 2018, T = 2017;
int k, n, m;
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
	e[idx] = a, w[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs() {
	queue<int> q;
	q.push(S);
	memset(d, 0, sizeof d);
	cur[S] = h[S], d[S] = 1;
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (!d[j] && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow <= limit; i = ne[i]) {
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(limit - flow, w[i]));
			if (!t) d[j] = 0;
			flow += t, w[i] -= t, w[i ^ 1] += t;
		}
	}
	return flow;
}
int dinic() {
	int re = 0, flow = 0;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> k >> n;
	for (int i = 1; i <= k; i ++ ) {
		int x;
		cin >> x;
		m += x;
		add(i + N, T, x);
	}
	for (int i = 1; i <= n; i ++ ) {
		int x;
		cin >> x;
		for (int j = 1; j <= x; j ++ ) {
			int y;
			cin >> y;
			add(i, y + N, 1);
		}
		add(S, i, 1);
	}
	int t = dinic();
	if (t != m) {
		cout << "No Solution!";
		return 0;
	}
	vector<int> s[N];
	for (int i = 0; i < idx; i ++ )
		if (e[i] > N && e[i] <= 2010 && e[i ^ 1] < N && !w[i]) s[e[i] - N].push_back(e[i ^ 1]);
	for (int i = 1; i <= k; i ++ ) {
		cout << i << ": ";
		for (int x : s[i]) cout << x << ' ';
		cout << '\n';
	}
	return 0;
}

<4> 星际转移问题

首先先考虑是否有解。飞船经过的一段航线 一定是可以互相到达的 (因为是周期性停靠)。所以使用 并查集 ,将所有能够互相到达的点全都并在一个集合,随后检查地球和月球是否互相可达。

若可行,要求最少天数,由于飞船每经过一条边都要花费 1 的时间,所以考虑使用 分层图 。每一层代表每一天的情况。若有一条航线中有 (u,x) 这条边,就把第 day1 层的 u 连向第 day 层的 x 。因为每次可以在空间站内等一天,所以还要向下一层的相同位置连一条边。所有月球节点均向汇点连 无穷大 的边,源点向第 0 天的地球节点连容量为 人数 的边。

初始加入第 0 天,此后每一天加入后再跑一边 残留网络的最大流 ,直到整体最大流达到 k ,说明达到答案。

// 星际转移问题
#include <bits/stdc++.h>
using namespace std;
const int N = 15010, M = 500010;
int S = N - 1, T = N - 2;
int e[M], ne[M], h[N], w[M], idx;
int fa[N], d[N], cur[N];
int n, m, k;
struct ship {
	int V, r, id[20];
}s[25];
int get(int x) {
	if (x == fa[x]) return x;
	return fa[x] = get(fa[x]);
}
int num(int x, int y) {	// 第 x 天,编号为 y 
	return x * (n + 2) + y;
}
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
}
bool bfs() {
	queue<int> q;
	q.push(S);
	memset(d, 0, sizeof d);
	d[S] = 1, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (!d[j] && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
 			}
		}
	}
	return false;
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(limit - flow, w[i]));
			if (!t) d[j] = 0;
			flow += t, w[i] -= t, w[i ^ 1] += t;
		}
	}
	return flow;
}
int dinic() {
	int re = 0, flow = 0;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m >> k;
	for (int i = 0; i <= n + 1; i ++ ) fa[i] = i;
	for (int i = 1; i <= m; i ++ ) {
		cin >> s[i].V >> s[i].r;
		for (int j = 1; j <= s[i].r; j ++ ) {
			cin >> s[i].id[j];
			if (s[i].id[j] == -1) s[i].id[j] = n + 1;
			if (j != 1) {
				int X = get(s[i].id[j]), Y = get(s[i].id[j - 1]);
				if (X != Y) fa[X] = Y;
			}
		}
	}
	if (get(0) != get(n + 1)) {
		cout << 0;
		return 0;
	}
	add(S, num(0, 0), k);
	add(num(0, n + 1), T, 1e9);
	int day = 0, res = 0;
	while (1) {
		day ++;add(num(day, n + 1), T, 1e9);
		for (int i = 0; i <= n; i ++ ) add(num(day - 1, i), num(day, i), 1e9);
		for (int i = 1; i <= m; i ++ ) {
			int r = s[i].r, a = s[i].id[(day - 1) % r + 1], b = s[i].id[day % r + 1];
			add(num(day - 1, a), num(day, b), s[i].V);
		}
		res += dinic();
		if (res == k) break;
	}
	cout << day;
	return 0;
}

<5> 最长不下降子序列问题

Q1: 最长不下降子序列长度:只需要按照 n2 跑一遍 DP 就行了。

Q2:考虑建立流网络,对于每一节点 u ,若存在节点 v 满足:

  • dpu=dpv+1

  • v<u

  • xvxu

则从 vu 连一条容量为一的边,若 dpu=1 则将源点向 u 连一条容量为一的边,若 dpu=s 则向汇点连一条容量为一的边,容易发现:流网络中的每一个可行流构成的序列都单调不下降,且因为每次 只能加 1 ,所以 每一个可行流都是一个最长不下降子序列

因为每个点只能被选一次,所以直接 拆点

Q3:只需要按照第二条一样的做法,由于 x1xn 可重复选,所以将他们两个内部流量设为极大值,不要忘记将他们与源点汇点的边容量也设为极大值。

(注:若最长不下降子序列长度为 1,Q3 中的做法会因为 x1xn 内部容量为极大值而使答案也变为极大值,所以要特判一下)

// 最长不下降子序列问题
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1010, M = N * N;
int S = N - 1, T = N - 2, len;
int w[M], e[M], ne[M], h[N], idx;
int dp[N], d[N], cur[N], a[N];
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
}
bool bfs() {
	queue<int> q;
	q.push(S);
	memset(d, 0, sizeof d);
	d[S] = 1, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (!d[j] && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	} 
	return false;
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(limit - flow, w[i]));
			if (!t) d[j] = 0;
			flow += t, w[i] -= t, w[i ^ 1] += t;
		}
	}
	return flow;
}
int dinic() {
	int re = 0, flow = 0;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
signed main() {
	memset(h, -1, sizeof h);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	memset(dp, -0x3f, sizeof dp);
	dp[0] = 0;
	for (int i = 1; i <= n; i ++ )
		for (int j = 0; j < i; j ++ )
			if (a[i] >= a[j]) dp[i] = max(dp[i], dp[j] + 1), len = max(len, dp[i]);
	cout << len << '\n';
	if (len == 1) {
	    cout << n << '\n' << n;
	    return 0;
	}
	for (int i = 1; i <= n; i ++ ) {
		if (dp[i] == 1) add(S, i, 1);
		if (dp[i] == len) add(i + n, T, 1);
		add(i, i + n, 1);
	}
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j < i; j ++ )
			if (a[j] <= a[i] && dp[j] + 1 == dp[i]) add(j + n, i, 1);
	cout << dinic() << '\n';
	memset(h, -1, sizeof h);
	idx = 0;
	for (int i = 1; i <= n; i ++ ) {
		if (dp[i] == 1) {
			if (i == 1 || i == n) add(S, i, 1e9);
			else add(S, i, 1);
		}
		if (dp[i] == len) {
			if (i == 1 || i == n) add(i + n, T, 1e9);
			else add(i + n, T, 1);
		}
		if (i != 1 && i != n) add(i, i + n, 1);
		else add(i, i + n, 1e9);
	}
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j < i; j ++ )
			if (a[j] <= a[i] && dp[j] + 1 == dp[i]) add(j + n, i, 1);
	cout << dinic();
	return 0;
}

<6> 魔术球问题:

隐式图

发现每一个球上下方至多各会一个球,所以考虑 插点 ,接着 二分图匹配

二分图左侧是出点,右侧是入点,观察可发现:最少柱子数=总球数-最大匹配数

接着一直尝试加球,直到最少柱子数大于给定数目。

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int S = N - 1, T = N - 2;
int h[N], cur[N], d[N], e[N], w[N], ne[N], idx, id[N], ID;
vector<int> s[N];
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
} 
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(w[i], limit - flow));
			if (!t) d[j] = -1;
			w[i] -= t, w[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}
bool bfs() {
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S);
	d[S] = 0, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int dinic() {
	int re = 0, flow = 0;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re; 
}
int main() {
	memset(h, -1, sizeof h);
	int n;cin >> n;
	int ans = 1, sum = 0;
	for (ans = 1; ; ans ++ ) {
		for (int i = 1; i < ans; i ++ )
			if (sqrt(ans + i) == (int)sqrt(ans + i))
				add(i, ans + 50000, 1);
		add(S, ans, 1);
		add(ans + 50000, T, 1);
		sum += dinic();
		if (ans - sum > n) break;
	}
	ans --;
	cout << ans << '\n';
	for (int i = 0; i < idx; i += 2 )
		if (e[i] == T && w[i]) id[e[i ^ 1] - 50000] = ++ ID, s[ID].push_back(e[i ^ 1] - 50000);
	for (int i = 0; i < idx; i += 2 )
		if (!w[i] && e[i] != T && e[i ^ 1] != S) id[e[i] - 50000] = id[e[i ^ 1]], s[id[e[i] - 50000]].push_back(e[i] - 50000);
	for (int i = 1; i <= n; i ++ ) {
		for (int x : s[i]) cout << x << ' ';
		cout << '\n';
	}
	return 0;
}

<7> 方格取数问题

板子题,很明显横坐标加纵坐标和为奇数只会与偶数的相邻,就把这两个分别放在 S,T 两边,中间边容量无穷。

//Dinic求最大流
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 1000010, INF = 1e9;
int h[N], e[M], ne[M], w[M], idx;
int d[N], cur[N], dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
int n, m, S = N - 1, T = N - 2;
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
} 
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(w[i], limit - flow));
			if (!t) d[j] = -1;
			w[i] -= t, w[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}
bool bfs() {
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S);
	d[S] = 0, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int dinic() {
	int re = 0, flow;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
signed main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	int sum = 0;
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ ) {
			int s;cin >> s;sum += s;
			if ((i + j) & 1) {
				add(S, i * m - m + j, s);
				for (int u = 0; u < 4; u ++ ) {
					int x = i + dx[u], y = j + dy[u];
					if (x >= 1 && x <= n && y >= 1 && y <= m) add(i * m - m + j, x * m - m + y, 1e9);
				}
			}
			else add(i * m - m + j, T, s);
		}
	printf("%d", sum - dinic());
	return 0;
}

<8> 骑士共存问题

发现如果将棋盘黑白染色,能互相踩到的点颜色一定不同,因此分二分图,直接跑最小割。ans = 总棋子数 - 最小割

#include <bits/stdc++.h>
using namespace std;
const int N = 210, M = 1000010;
int n, m, S = N * N - 1, T = N * N - 2;
int e[M], ne[M], h[N * N], w[M], idx, color[N][N];
int d[N * N], cur[N * N], jin[N][N], ran[N][N];
bool st[N * N];
int dx[8] = {-2, -2, -1, 1, 2, 2, 1, -1}, dy[8] = {-1, 1, 2, 2, 1, -1, -2, -2};
int Dx[2] = {0, 1}, Dy[2] = {1, 0};
int zhuan(int x, int y) {
	return n * (x - 1) + y;
}
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
	e[idx] = a, w[idx] = 0, ne[idx] = h[b], h[b] = idx ++; 
}
void build() {
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 1; j <= n; j ++ ) {
			if (color[i][j] == 1) {
				for (int u = 0; u < 8; u ++ ) {
					int x = i + dx[u], y = j + dy[u];
					if (x < 1 || x > n || y < 1 || y > n || jin[i][j] || jin[x][y]) continue;
					add(zhuan(i, j), zhuan(x, y), 1);
				}
			}
		}
	}
}
void dfs(int x, int y, int c) {
	ran[x][y] = 1;
	color[x][y] = c;
	for (int u = 0; u < 2; u ++ ) {
		int xx = x + Dx[u], yy = y + Dy[u];
		if (xx >= 1 && xx <= n && yy >= 1 && yy <= n && !ran[xx][yy]) dfs(xx, yy, 3 - c);
	}
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(w[i], limit - flow));
			if (!t) d[j] = -1;
			w[i] -= t, w[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}
bool bfs() {
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S);
	d[S] = 0, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int dinic() {
	int re = 0, flow;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= m; i ++ ) {
		int X, Y;
		scanf("%d%d", &X, &Y);
		jin[X][Y] = 1;
	}
	dfs(1, 1, 1);
	build();
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
			if (color[i][j] == 1) add(S, zhuan(i, j), 1);
			else add(zhuan(i, j), T, 1);
	cout << n * n - dinic() - m;
	return 0;
}

<9> 太空飞行计划问题

最大权闭合图板子。

实验放在 S 集合里,器材放在 T 集合里。向源汇点连的边都是其价值或耗费。每项实验与其所需的器材连容量无穷的边,发现要么不要这一项实验,要么要这一项实验,支付所有需要的器材。发现很像最小割,答案为 做完所有实验得到的总价值 - 最小割

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 55010, M = 2000010;
int a[N];
int e[M], ne[M], w[M], h[N], idx; 
int d[N], cur[N], st[N];
int n, m, ans, S = N - 1, T = N - 2;
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
}
bool bfs() {
	queue<int> q;
	q.push(S);
	memset(d, 0, sizeof d);
	d[S] = 1, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (!d[j] && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(w[i], limit - flow));
			if (!t) d[j] = 0;
			w[i] -= t, w[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}
int dinic() {
	int re = 0, flow;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
void dfs(int u) {
    st[u] = true;
    for (int i = h[u]; ~i; i = ne[i])
        if (!st[e[i]] && w[i])
            dfs(e[i]);
}
signed main() {
	memset(h, -1, sizeof h);
	cin >> m >> n;
	getchar();
    for (int i = 1; i <= m; i ++ )
    {
        int w, id;
        string line;
        getline(cin, line);
        stringstream ssin(line);
        ssin >> w;
        add(S, i + n, w);
        while (ssin >> id) add(i + n, id, 1e9);
        ans += w;
    }
	for (int i = 1; i <= n; i ++ ) cin >> a[i], add(i, T, a[i]);
	ans = ans - dinic();
	dfs(S);
	for (int i = n + 1; i <= m + n; i ++ )
		if (st[i]) cout << i - n << ' ';
	cout << '\n';
	for (int i = 1; i <= n; i ++ )
		if (st[i]) cout << i << ' ';
	cout << '\n' << ans;
	return 0;
}

<10> 运输问题

把原问题转化为网络流,发现所有合法情况都是最大流,要求这中间的最小花费,很明显是最小费用最大流。

#include <bits/stdc++.h>
using namespace std;
const int N = 160, M = 10310;
int n, m, S = N - 1, T = N - 2;
int h[N], ne[M], e[M], w[M], f[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d) {
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa() {
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e8;
    while (hh != tt) {
        int u = q[hh ++];
        if (hh == N) hh = 0;
        st[u] = 0;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] > d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(incf[u], f[i]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }
    return incf[T] > 0;
}
int EK() {
    int cost = 0;
    while (spfa()) {
        int t = incf[T];
        cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return cost;
}
int main() {
    cin >> m >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= m; i ++ ) {
        int s;cin >> s;
        add(S, i, s, 0);
    }
    for (int i = 1; i <= n; i ++ ) {
        int s;cin >> s;
        add(i + m, T, s, 0);
    }
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= n; j ++ ) {
            int s;cin >> s;
            add(i, j + m, 1e8, s);
        }
    printf("%d\n", EK());
    for (int i = 0; i < idx; i += 2 ) {
        f[i] += f[i ^ 1], f[i ^ 1] = 0;
        w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
    }
    printf("%d", -EK());
    return 0;
}

<11> 负载平衡问题

可以发现一个规律:少于平均值的最后一定不会给到多余平均值的,因此多余平均值的放在原点一边,少于平均值的放在汇点一边。源点向第 i 个点连边的容量为:a[i] - 平均值,汇点反过来。

首先必须要每个仓库数量相同,即最大流,求最小次数,其实就是最小费用最大流。

#include <bits/stdc++.h>
using namespace std;
const int N = 210, M = N * N * 3 + 10;
int n, S = N - 1, T = N - 2, sum;
int e[M], ne[M], h[N], w[M], f[M], idx;
int a[N], d[N], incf[N], q[N], pre[N];
bool st[N];
void add(int a, int b, int c, int d) {
    e[idx] = b, ne[idx] = h[a], w[idx] = d, f[idx] = c, h[a] = idx ++;
    e[idx] = a, ne[idx] = h[b], w[idx] = -d, f[idx] = 0, h[b] = idx ++;
}
bool spfa() {
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e8;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;
        st[u] = 0;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] > d[u] + w[i]) {
                d[j] = d[u] + w[i];
                incf[j] = min(incf[u], f[i]);
                pre[j] = i;
                if (!st[j]) {
                    st[j] = 1;
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
    return incf[T] > 0;
}
int EK() {
    int cost = 0;
    while (spfa()) {
        int t = incf[T];
        cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i] ^ 1] += t;
            f[pre[i]] -= t;
        }
    }
    return cost;
}
int main() {
    memset(h, -1, sizeof h);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i], sum += a[i];sum /= n;
    for (int i = 1; i <= n; i ++ ) {
        add(i, i < n ? i + 1 : 1, 1e8, 1);
        add(i, i > 1 ? i - 1 : n, 1e8, 1);
        if (a[i] > sum) add(S, i, a[i] - sum, 0);
        else if (a[i] < sum) add(i, T, sum - a[i], 0);
    }
    cout << EK();
    return 0;
}

<12> 分配问题

二分图。

人放左边,工作放右边,每人只能完成一样工作且每样工作都要有人做,所以源点连向人的容量为 1 ,工作向汇点连的边容量也为 1

每人做每项工作的效率作为他们之间连边的价值。容易发现永远是最大流,所以求最小费用最大流即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = N * N;
int c[N][N], n, S = N - 1, T = N - 2;
int h[N], e[M], ne[M], w[M], f[M], idx;
int d[N], q[N], incf[N], pre[N];
bool st[N];
void add(int a, int b, int c, int d) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, w[idx] = d, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, w[idx] = -d, h[b] = idx ++;
}
bool spfa() {
	memset(d, 0x3f, sizeof d);
	memset(incf, 0, sizeof incf);
	int hh = 0, tt = 1;
	q[0] = S, d[S] = 0, incf[S] = 1e9;
	while (hh != tt) {
		int u = q[hh ++ ];
		st[u] = 0;
		if (hh == N) hh = 0;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (f[i] && d[j] > d[u] + w[i]) {
				d[j] = d[u] + w[i];
				pre[j] = i;
				incf[j] = min(incf[u], f[i]);
				if (!st[j]) {
					st[j] = 1;
					q[tt ++ ] = j;
					if (tt == N) tt = 0;
				}
			}
		}
	}
	return incf[T] > 0;
}
int EK() {
	int cost = 0;
	while (spfa()) {
		int t = incf[T];
		cost += t * d[T];
		for (int i = T; i != S; i = e[pre[i] ^ 1])
			f[pre[i]] -= t, f[pre[i] ^ 1] += t;
	}
	return cost;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n;
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ ) {
			cin >> c[i][j]; 
			add(i, j + n, 1, c[i][j]);
		}
	for (int i = 1; i <= n; i ++ ) add(S, i, 1, 0), add(i + n, T, 1, 0);
	printf("%d\n", EK());
	for (int i = 0; i < idx; i += 2) {
		f[i] += f[i ^ 1], f[i ^ 1] = 0;
		w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
	}
	printf("%d", -EK());
	return 0;
}

<13> 数字梯形问题

费用流板子题,拆点,通过边和点的流量来限制经过次数。

#include <bits/stdc++.h>
using namespace std;
const int K = 45, N = K * K, M = N * N;
int a[K][K], b[K][K], cnt, S = N - 1, T = N - 2;
int h[N], e[M], ne[M], w[M], f[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d) {
    e[idx] = b, ne[idx] = h[a], w[idx] = d, f[idx] = c, h[a] = idx ++;
    e[idx] = a, ne[idx] = h[b], w[idx] = -d, f[idx] = 0, h[b] = idx ++;
}
int spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;st[u]  = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] < d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(f[i], incf[u]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = 1;
                }
            } 
        }
    }
    return incf[T] > 0;
}
int EK() {
    int re = 0;
    while (spfa()) {
        int t = incf[T];
        re += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return re;
}
int main() {
    memset(h, -1, sizeof h);
    int m, n;cin >> m >> n;
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            cin >> a[i][j], b[i][j] = ++cnt;
    for (int i = 1; i < n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j] + cnt, b[i + 1][j], 1, 0), add(b[i][j] + cnt, b[i + 1][j + 1], 1, 0);
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j], b[i][j] + cnt, 1, a[i][j]);
    for (int i = 1; i <= m; i ++ ) add(S, i, 1, 0);
    for (int i = cnt; i > cnt - m - n; i -- ) add(i + cnt, T, 1e9, 0);
    cout << EK() << '\n';
    memset(h, -1, sizeof h), idx = 0;
    for (int i = 1; i < n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j] + cnt, b[i + 1][j], 1, 0), add(b[i][j] + cnt, b[i + 1][j + 1], 1, 0);
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j], b[i][j] + cnt, 1e9, a[i][j]);
    for (int i = 1; i <= m; i ++ ) add(S, i, 1, 0);
    for (int i = cnt; i > cnt - m - n; i -- ) add(i + cnt, T, 1e9, 0);
    cout << EK() << '\n';
    memset(h, -1, sizeof h), idx = 0;
    for (int i = 1; i < n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j] + cnt, b[i + 1][j], 1e9, 0), add(b[i][j] + cnt, b[i + 1][j + 1], 1e9, 0);
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m + i - 1; j ++ )
            add(b[i][j], b[i][j] + cnt, 1e9, a[i][j]);
    for (int i = 1; i <= m; i ++ ) add(S, i, 1, 0);
    for (int i = cnt; i > cnt - m - n; i -- ) add(i + cnt, T, 1e9, 0);
    cout << EK() << '\n';
    return 0;
} 

<14> 深海机器人问题:

板子题,不想说了。

#include <bits/stdc++.h>
using namespace std;
const int N = 300, M = 1000010;
int n, m, A, B, S = N - 1, T = N - 2;
int e[M], ne[M], h[N], w[M], f[M], idx; 
int d[N], q[N], incf[N], st[N], pre[N];
int z(int x, int y) {
    return x * (m + 1) + y;
}
void add(int a, int b, int c, int d) {
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int ver = e[i];
            if (f[i] && d[ver] < d[t] + w[i]) {
                d[ver] = d[t] + w[i];
                pre[ver] = i;
                incf[ver] = min(f[i], incf[t]);
                if (!st[ver]) {
                    q[tt ++ ] = ver;
                    if (tt == N) tt = 0;
                    st[ver] = true;
                }
            }
        }
    }
    return incf[T] > 0;
}
int EK() {
    int cost = 0;
    while (spfa()) {
        int t = incf[T];
        cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return cost;
}
int main() {
    memset(h, -1, sizeof h);
    cin >> A >> B >> n >> m;
    for (int i = 0; i <= n; i ++ )
        for (int j = 0; j < m; j ++ ) {
            int s;cin >> s;
            add(z(i, j), z(i, j + 1), 1, s);
            add(z(i, j), z(i, j + 1), 1e9, 0);
        }
    for (int j = 0; j <= m; j ++ )
        for (int i = 0; i < n; i ++ ) {
            int s;cin >> s;
            add(z(i, j), z(i + 1, j), 1, s);
            add(z(i, j), z(i + 1, j), 1e9, 0);
        }
    for (int i = 1; i <= A; i ++ ) {
        int k, x, y;cin >> k >> x >> y;
        add(S, z(x, y), k, 0);
    }
    for (int i = 1; i <= B; i ++ ) {
        int k, x, y;cin >> k >> x >> y;
        add(z(x, y), T, k, 0);
    }
    cout << EK();
    return 0;
}

<15> 餐巾计划问题:

偷懒,直接放图。

#include <bits/stdc++.h>
using namespace std;
const int N = 4010, M = 1000010;
#define int long long
int n, m, A, B, S = N - 1, T = N - 2;
int e[M], ne[M], h[N], w[M], f[M], idx; 
int d[N], q[N], incf[N], st[N], pre[N], r[N];
void add(int a, int b, int c, int d) {
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa() {
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int ver = e[i];
            if (f[i] && d[ver] > d[t] + w[i]) {
                d[ver] = d[t] + w[i];
                pre[ver] = i;
                incf[ver] = min(f[i], incf[t]);
                if (!st[ver]) {
                    q[tt ++ ] = ver;
                    if (tt == N) tt = 0;
                    st[ver] = true;
                }
            }
        }
    }
    return incf[T] > 0;
}
int EK() {
    int cost = 0;
    while (spfa()) {
        int t = incf[T];
        cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return cost;
}
signed main() {
    memset(h, -1, sizeof h);
    int n, m, s1, p1, s2, p2;cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> r[i];
	cin >> m >> s1 >> p1 >> s2 >> p2;
    for (int i = 1; i <= n; i ++ ) add(S, i, r[i], 0), add(i + n, T, r[i], 0), add(S, i + n, 1e9, m);
    for (int i = 1; i < n; i ++ ) add(i, i + 1, 1e9, 0);
    for (int i = s1 + 1; i <= n; i ++ ) add(i - s1, i + n, 1e9, p1);
    for (int i = s2 + 1; i <= n; i ++ ) add(i - s2, i + n, 1e9, p2);
    cout << EK();
    return 0;
}

<16> 最小路径覆盖问题

注意!不是板题。

初看这道题,没有什么思路,但是发现:标签是网络流 1n150

这种数据范围可以推断出很有可能是网络流。

首先不可能是最小割,因为这是分成多条路径,而最小割是解决不重的两个集合。

其次不可能是费用流,因为很明显不可能

所以是最大流,但可以发现求的是最小路径数,正难则反。考虑让一开始所有点都单独是一条路径,每使用一条边就可以少一条路径,每个点只能进一次出一次。容易发现要拆点,分别方二分图两边,然后做最大匹配。

答案为总点数减最大匹配。

#include <bits/stdc++.h>
using namespace std;
const int N = 310, M = 100010;
int n, m, S = N - 1, T = N - 2;
int e[M], ne[M], h[N], w[M], idx;
int cur[N], d[N], st[N], du[N], Ne[N];
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	e[idx] = a, ne[idx] = h[b], w[idx] = 0, h[b] = idx ++;
}
int find(int u, int limit) {
	if (u == T) return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		cur[u] = i;
		int j = e[i];
		if (d[j] == d[u] + 1 && w[i]) {
			int t = find(j, min(w[i], limit - flow));
			if (!t) d[j] = -1;
			w[i] -= t, w[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}
bool bfs() {
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S);
	d[S] = 0, cur[S] = h[S];
	while (q.size()) {
		int u = q.front();q.pop();
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && w[i]) {
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if (j == T) return true;
				q.push(j);
			}
		}
	}
	return false;
}
int dinic() {
	int re = 0, flow;
	while (bfs()) while (flow = find(S, 1e9)) re += flow;
	return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= m; i ++ ) {
		int x, y;cin >> x >> y;
		add(x, y + n, 1);
	}
	for (int i = 1; i <= n; i ++ ) add(S, i, 1), add(i + n, T, 1);
	int ans = n - dinic();
	for (int i = 1; i <= n; i ++ )
		for (int j = h[i]; j != -1; j = ne[j]) if (e[j] != S && !w[j]) {
			Ne[i] = e[j] - n;du[e[j] - n] ++;
		}
	for (int i = 1; i <= n; i ++ )
		if (!st[i] && !du[i]) {
			int j = i;
			while (j) {
				st[j] = 1;
				cout << j << ' ';
				j = Ne[j];
			}
			cout << '\n';
		}
	cout << ans;
	return 0;
} 

<17> 软件补丁问题

我也不知道这个玩意为什么是网络流24题里头的,但是为了完成目标我还是做了。

发现 n20 。考虑状压。

求最小值可以直接转化成求最短路用DJ,状态作为点,边为状态之间的转换。

用位运算考虑一下能不能转就行了。

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2000010, M = 110;
int b1[M], b2[M], f1[M], f2[M], t[M];
int dist[N], st[N];
char c[M];
int n, m;
int DJ() {
	priority_queue<PII, vector<PII>, greater<PII> > q;
	memset(dist, 0x3f, sizeof dist);
	q.push({0, (1 << n) - 1});dist[(1 << n) - 1] = 0;
	while (q.size()) {
		int u = q.top().second;q.pop();
		if (u == 0) return dist[u];
		if (st[u]) continue;st[u] = 1;
		for (int i = 1; i <= m; i ++ )
			if ((u | b1[i]) == u && (u & b2[i]) == u) {
				int j = (u & f1[i]) | f2[i];
				if (dist[j] > dist[u] + t[i]) {
					dist[j] = dist[u] + t[i];
					q.push({dist[j], j});
				}
			}
	}
	return 0;
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i ++ ) {
		cin >> t[i];b2[i] = f1[i] = (1 << n) - 1;
		cin >> c + 1;
		for (int j = 1; j <= n; j ++ ) {
			if (c[j] == '+') b1[i] += (1 << j - 1);
			if (c[j] == '-') b2[i] -= (1 << j - 1);
		}
		cin >> c + 1;
		for (int j = 1; j <= n; j ++ ) {
			if (c[j] == '-') f1[i] -= (1 << j - 1);
			if (c[j] == '+') f2[i] += (1 << j - 1);
		}
	}
	cout << DJ();
	return 0;
}

<18> 航空路线问题

每个点只能走一次,想到网络流拆点进行限制。求最多经过的点数,想到费用流。

拆点时 1n 号点的内部流量为 2,因为要经过两次,其余点为 1,费用都为 1

边一般情况下都只会经过一次,除非只有 1 号点和 n 号点连接,这个特判就行了,我也没想到什么好方法。

边都没有费用,费用是按点来记的。

注意:网络流一般要求只建单向边,所以我们考虑把原题改为找两条 1n 的路,然后把所有的边变成经度小的连向经度大的。

最后输出两边 dfs 就可以啦。

对了,还有一个很扯淡的事情。

由于题目太水,貌似不判断是否是经度小的连向经度大的也能过,不过会被手搓的数据卡掉:

4 4
a
b
c
d
d c
d b
b a
c a
#include <bits/stdc++.h>
using namespace std;
const int N = 210, M = 100010;
int n, m, S, T, idx;
int h[N], e[M], ne[M], w[M], f[M];
int q[M], incf[N], d[N], st[N], pre[N];
int top, stk[N];
string s[N];
map<string, int> mp;
void add(int a, int b, int c, int d) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, w[idx] = d, h[a] = idx ++; 
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, w[idx] = -d, h[b] = idx ++;
}
int spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;st[u]  = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] < d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(f[i], incf[u]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = 1;
                }
            } 
        }
    }
    return incf[T] > 0;
}
int EK() {
    int re = 0;
    while (spfa()) {
        int t = incf[T];
        re += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return re;
}
void dfs(int u) {
	stk[++top] = u, st[u] = 1;
	if (u == n) return; 
	for (int i = h[u + n]; i != -1; i = ne[i]) if (!f[i]) {
		if (e[i] < n && e[i] != 1 && st[e[i]] || e[i] == u) continue;
		dfs(e[i]);
		return;
	}
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		cin >> s[i];
		mp[s[i]] = i;
	}
	S = 1, T = n * 2;
	add(1, n + 1, 2, 1);
	add(n, n * 2, 2, 1);
	for (int i = 2; i < n; i ++ ) add(i, i + n, 1, 1); 
	for (int i = 1; i <= m; i ++ ) {
		string a, b;cin >> a >> b;
		if (mp[a] > mp[b]) swap(a, b);
		add(mp[a] + n, mp[b], 1, 0);
	}
	int ans = EK();
	if (ans == 2) {
		cout << 2 << '\n';
		cout << s[1] << '\n' << s[n] << '\n' << s[1];
		return 0;
	}
	if (f[0]) {
		cout << "No Solution!";
		return 0;
	}
	cout << ans - 2 << '\n';
	memset(st, 0, sizeof st);
	dfs(S);
	for (int i = 1; i <= top; i ++ ) cout << s[stk[i]] << '\n';
	top = 0;dfs(S);
	for (int i = top - 1; i; i -- ) cout << s[stk[i]] << '\n';
	return 0;
}

<19> 孤岛营救问题

和软件补丁差不多,都是将不同的状态作为不同的节点。因为 P10,所以对于获得钥匙种类进行状态压缩。

每个单元格的不同状态都要作为一个点,然后跑最短路就可以了。

有个小细节:每个点可能有多个钥匙。

#include <bits/stdc++.h>
using namespace std;
const int N = 12;
int g[N][N][N][N], s[N][N];
int dist[N][N][1100], st[N][N][1100];
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
int n, m, p, k, S;
struct P {
	int v, x, y, z;
	bool operator < (const P &b) const { return v > b.v; }
};
int DJ() {
	memset(dist, 0x3f, sizeof dist);
	dist[1][1][s[1][1]] = 0;
	priority_queue<P> q;
	q.push({0, 1, 1, s[1][1]});
	while (q.size()) {
		auto x = q.top().x, y = q.top().y, z = q.top().z;q.pop();
		if (x == n && y == m) return dist[x][y][z];
		if (st[x][y][z]) continue;st[x][y][z] = 1;
		for (int u = 0; u < 4; u ++ ) {
			int xx = x + dx[u], yy = y + dy[u];
			if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
			int X = g[x][y][xx][yy];
			if (X != -1 && (!X || (z ^ (1 << X - 1)) > z)) continue;
			int Z = z | s[xx][yy];
			if (dist[x][y][z] + 1 < dist[xx][yy][Z]) {
				dist[xx][yy][Z] = dist[x][y][z] + 1;
				q.push({dist[xx][yy][Z], xx, yy, Z});
			}
		}
	} 
	return -1;
}
int main() {
	memset(g, -1, sizeof g);
	cin >> n >> m >> p >> k;
	for (int i = 1; i <= k; i ++ ) {
		int X1, Y1, X2, Y2, x;
		cin >> X1 >> Y1 >> X2 >> Y2 >> x;
		g[X2][Y2][X1][Y1] = g[X1][Y1][X2][Y2] = x;
	}
	cin >> S;
	for (int i = 1; i <= S; i ++ ) {
		int x, y, z;cin >> x >> y >> z;
		s[x][y] |= (1 << z - 1);
	}
	cout << DJ();
	return 0;
}

<20> 火星探险问题

发现好多网络流的题难点在于输出方案。

先讲建图。

对于点有限制,想到拆点。

是在到达机器人最多的情况下采集的石头最多,明显最大费用最大流。

  • 若平坦无障碍,在入点与出点间连一条容量为 q,费用为 0 的边即可。
  • 若有障碍,入点与出点间不连边。
  • 若有石头,由于石头只能取一次,所以入点与出点间连一条容量为 1,费用为 1 的边。剩余机器人还有可能再次经过,所以再连一条容量为 q1,费用为 0 的边。

对于单元格之间的路径直接连一条容量为 q,费用为 0 的边即可,然后跑费用流。

输出方案有点有趣。

由于我们发现正向边的容量并不相同且不一定为 1。在种种情况的影响下,从原点开始遍历十分麻烦。

所以考虑从汇点反着来,如果某条连接单元格的边有容量那就是走过。

贪心的考虑,当前只要有这样的边可以走,就直接走,并不会对结果有任何影响。这很简单,我就不多赘述。

所以只需要从汇点遍历到原点,没经过一条容量不为 0 的边,就把其容量减一(点内部的边不用管,自己看怎么处理),然后倒序输出即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 1000010;
int num, n, m, idx, S, T, W;
int h[N], e[M], ne[M], w[M], f[M];
int q[M], incf[N], d[N], st[N], pre[N];
int stk[N], top;
void add(int a, int b, int c, int d) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, w[idx] = d, h[a] = idx ++; 
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, w[idx] = -d, h[b] = idx ++;
}
int spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;st[u] = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] < d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(f[i], incf[u]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = 1;
                }
            } 
        }
    }
    return incf[T] > 0;
}
int EK() {
    int re = 0;
    while (spfa()) {
        int t = incf[T];
        re += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return re;
}
int z(int x, int y) {
	return (x - 1) * m + y; 
}
void dfs(int u) {
	for (int i = h[u - W]; i != -1; i = ne[i]) if (e[i] != u && f[i]) {
		f[i] --;
		if (e[i] == u - m) stk[++top] = 0;
		else stk[++top] = 1;
		dfs(e[i]);
		return;
	}
}
int main() {
	memset(h, -1, sizeof h);
	cin >> num >> m >> n;W = n * m;
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ ) {
			int x;cin >> x;
			if (!x) add(z(i, j), z(i, j) + W, num, 0);
			if (x == 2) add(z(i, j), z(i, j) + W, 1, 1), add(z(i, j), z(i, j) + W, num - 1, 0);
		}
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ ) {
			if (i != n) add(z(i, j) + W, z(i + 1, j), num, 0);
			if (j != m) add(z(i, j) + W, z(i, j + 1), num, 0);
		}
	S = 1, T = W * 2;
	int ans = EK();
	for (int i = 1; i <= ans; i ++ ) {
		top = 0;
		dfs(T);
		for (int j = top; j; j -- ) cout << i << ' ' << stk[j] << '\n';
	}
	return 0;
}

<21> 汽车加油行驶问题

这是最短路叭······

发现有两个限制条件:费用与油量。要求的是最小费用,所以边权应该是费用。

那么油量怎么处理呢?

发现 2k10。所以发现可以考虑分层图,按油量分层。

em……就这样啦。

#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 1000010;
typedef pair<int, int> PII;
int h[N], e[M], ne[M], w[M], idx;
int dist[N], st[N], a[110][110];
int n, m, A, B, C;
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
int num(int x, int y, int z) {
	return n * n * z + (x - 1) * n + y;
}
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
int DJ() {
	memset(dist, 0x3f, sizeof dist);
	dist[num(1, 1, m)] = 0;
	priority_queue<PII, vector<PII>, greater<PII> > q;
	q.push({0, num(1, 1, m)});
	while (q.size()) {
		int u = q.top().second;q.pop();
		if (st[u]) continue;st[u] = 1;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (dist[j] > dist[u] + w[i]) {
				dist[j] = dist[u] + w[i];
				q.push({dist[j], j});
			}
		}
	}
	int re = 1e9;
	for (int i = 0; i <= m; i ++ ) re = min(re, dist[num(n, n, i)]);
	return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m >> A >> B >> C;
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ ) {
			cin >> a[i][j];
			for (int k = 0; k < m; k ++ )
				add(num(i, j, k), num(i, j, m), A + C * (!a[i][j]));
		}
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
			for (int u = 0; u < 4; u ++ ) {
				int x = i + dx[u], y = j + dy[u];
				if (x < 1 || x > n || y < 1 || y > n) continue;
				if (a[i][j]) add(num(i, j, m), num(x, y, m - 1), (dx[u] == -1 || dy[u] == -1) * B);
				else for (int l = 1; l <= m; l ++ )
					add(num(i, j, l), num(x, y, l - 1), (dx[u] == -1 || dy[u] == -1) * B);
			}
	cout << DJ();
	return 0;
}

<22> 最长k可重区间集问题

发现很多题解其实讲的太复杂了。

发现在最优情况下,所有区间一定可以分为 k 层,每层中区间都不重合。

发现不相交的区间可以分为同一层,而相交的区间一定不行。

1 流量代表一层。每个区间向后方与其不相交的区间连容量 1,费用 0,的边。

由于要统计区间的贡献,所以将区间拆点(原本将每个区间看成一个点)。

费用为 len,容量为 1,因为只能被分在一层中。

源点向区间左端点连一条容量 1 的边,汇点同理。

由于要控制层数,所以还要将源点拆点,限制流量为 k

跑最大费用最大流,完事儿~

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 2000010;
int n, m, idx, l[N], r[N];
int S = N - 1, SS = N - 2, T = N - 3; 
int h[N], e[M], ne[M], w[M], f[M];
int q[M], incf[N], d[N], st[N], pre[N];
int stk[N], top;
void add(int a, int b, int c, int d) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, w[idx] = d, h[a] = idx ++; 
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, w[idx] = -d, h[b] = idx ++;
}
int spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;st[u] = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] < d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(f[i], incf[u]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = 1;
                }
            } 
        }
    }
    return incf[T] > 0;
}
int EK() {
    int re = 0;
    while (spfa()) {
        int t = incf[T];
        re += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> l[i] >> r[i];
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
			if (l[j] >= r[i]) add(i + n, j, 1, 0);
	for (int i = 1; i <= n; i ++ ) {
		add(i, i + n, 1, r[i] - l[i]);
		add(SS, i, 1, 0);
		add(i + n, T, 1, 0);
	}
	add(S, SS, m, 0);
	cout << EK();
	return 0;
}

<23> 最长k可重线段集问题

这和上一道题有区别吗?只需要管横坐标就行。判断是否相交再特判一下两条线段都垂直于横坐标算相交就行了。

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 2000010;
int n, m, idx, l[N], r[N], len[N];
int S = N - 1, SS = N - 2, T = N - 3; 
int h[N], e[M], ne[M], w[M], f[M];
int q[M], incf[N], d[N], st[N], pre[N];
int stk[N], top;
void add(int a, int b, int c, int d) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, w[idx] = d, h[a] = idx ++; 
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, w[idx] = -d, h[b] = idx ++;
}
int spfa() {
    int hh = 0, tt = 1;
    memset(d, -0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = 1e9;
    while (hh != tt) {
        int u = q[hh ++ ];
        if (hh == N) hh = 0;st[u] = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (f[i] && d[j] < d[u] + w[i]) {
                d[j] = d[u] + w[i];
                pre[j] = i;
                incf[j] = min(f[i], incf[u]);
                if (!st[j]) {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = 1;
                }
            } 
        }
    }
    return incf[T] > 0;
}
int EK() {
    int re = 0;
    while (spfa()) {
        int t = incf[T];
        re += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
    return re;
}
int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		int a, b, c, d;cin >> a >> b >> c >> d;
		l[i] = min(a, c);r[i] = max(a, c);
		len[i] = int(sqrt(1ll * (a - c) * (a - c) + 1ll * (b - d) * (b - d))); 
	}
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
			if (l[j] > r[i] || l[j] == r[i] && (r[i] != l[i] || r[j] != l[j])) add(i + n, j, 1, 0);
	for (int i = 1; i <= n; i ++ ) {
		add(i, i + n, 1, len[i]);
		add(SS, i, 1, 0);
		add(i + n, T, 1, 0);
	}
	add(S, SS, m, 0);
	cout << EK();
	return 0;
}

<24> 机器人路径规划问题

灰题。

无正解。

posted @   paper_octopus  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示