网络流 24 题。
<1> 飞行员配对方案问题
妥妥 二分图匹配,外籍飞行员与英国飞行员分别在两侧,边的流量均为 1。
用
// 飞行员配对方案问题
#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 的边。
每个单位连向源点容量都为 该单位人数 ,每张餐桌连向汇点容量都为 该餐桌可容纳人数 。
使用
// 圆桌问题
#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 的时间,所以考虑使用 分层图 。每一层代表每一天的情况。若有一条航线中有
初始加入第 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: 最长不下降子序列长度:只需要按照
Q2:考虑建立流网络,对于每一节点
则从
因为每个点只能被选一次,所以直接 拆点 。
Q3:只需要按照第二条一样的做法,由于
(注:若最长不下降子序列长度为 1,Q3 中的做法会因为
// 最长不下降子序列问题
#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> 方格取数问题
板子题,很明显横坐标加纵坐标和为奇数只会与偶数的相邻,就把这两个分别放在
//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> 太空飞行计划问题
最大权闭合图板子。
实验放在 做完所有实验得到的总价值 - 最小割
。
#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> 负载平衡问题
可以发现一个规律:少于平均值的最后一定不会给到多余平均值的,因此多余平均值的放在原点一边,少于平均值的放在汇点一边。源点向第 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> 分配问题
二分图。
人放左边,工作放右边,每人只能完成一样工作且每样工作都要有人做,所以源点连向人的容量为
每人做每项工作的效率作为他们之间连边的价值。容易发现永远是最大流,所以求最小费用最大流即可。
#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> 最小路径覆盖问题
注意!不是板题。
初看这道题,没有什么思路,但是发现:标签是网络流
这种数据范围可以推断出很有可能是网络流。
首先不可能是最小割,因为这是分成多条路径,而最小割是解决不重的两个集合。
其次不可能是费用流,因为很明显不可能。
所以是最大流,但可以发现求的是最小路径数,正难则反。考虑让一开始所有点都单独是一条路径,每使用一条边就可以少一条路径,每个点只能进一次出一次。容易发现要拆点,分别方二分图两边,然后做最大匹配。
答案为总点数减最大匹配。
#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题里头的,但是为了完成目标我还是做了。
发现
求最小值可以直接转化成求最短路用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> 航空路线问题
每个点只能走一次,想到网络流拆点进行限制。求最多经过的点数,想到费用流。
拆点时
边一般情况下都只会经过一次,除非只有
边都没有费用,费用是按点来记的。
注意:网络流一般要求只建单向边,所以我们考虑把原题改为找两条
最后输出两边 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> 孤岛营救问题
和软件补丁差不多,都是将不同的状态作为不同的节点。因为
每个单元格的不同状态都要作为一个点,然后跑最短路就可以了。
有个小细节:每个点可能有多个钥匙。
#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> 火星探险问题
发现好多网络流的题难点在于输出方案。
先讲建图。
对于点有限制,想到拆点。
是在到达机器人最多的情况下采集的石头最多,明显最大费用最大流。
- 若平坦无障碍,在入点与出点间连一条容量为
,费用为 的边即可。 - 若有障碍,入点与出点间不连边。
- 若有石头,由于石头只能取一次,所以入点与出点间连一条容量为
,费用为 的边。剩余机器人还有可能再次经过,所以再连一条容量为 ,费用为 的边。
对于单元格之间的路径直接连一条容量为
输出方案有点有趣。
由于我们发现正向边的容量并不相同且不一定为
所以考虑从汇点反着来,如果某条连接单元格的边有容量那就是走过。
贪心的考虑,当前只要有这样的边可以走,就直接走,并不会对结果有任何影响。这很简单,我就不多赘述。
所以只需要从汇点遍历到原点,没经过一条容量不为
#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> 汽车加油行驶问题
这是最短路叭······
发现有两个限制条件:费用与油量。要求的是最小费用,所以边权应该是费用。
那么油量怎么处理呢?
发现
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可重区间集问题
发现很多题解其实讲的太复杂了。
发现在最优情况下,所有区间一定可以分为
发现不相交的区间可以分为同一层,而相交的区间一定不行。
每
由于要统计区间的贡献,所以将区间拆点(原本将每个区间看成一个点)。
费用为
源点向区间左端点连一条容量
由于要控制层数,所以还要将源点拆点,限制流量为
跑最大费用最大流,完事儿~
#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> 机器人路径规划问题
灰题。
无正解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话