网络流刷题笔记
本文使用的是 Trovelrd 的文章里的例题,在此鸣谢。
P2764 最小路径覆盖问题
网络流二十四题之一。
考虑对于图上的每个节点拆点,拆成入点和出点,所有入点和源点连边,所有出点和汇点连边,容量为 \(1\)。
对于原图中的一条边 \((u,v)\),将 \(u\) 的入点和 \(v\) 的出点连边即可,容量为 \(1\)。
这样建出来的图,必须满足每个入点只能有一个出点连,每个出点也只能被一个入点连,跑二分图最大匹配即可。
每个增广路对应两个能合并的节点,则最大流就是能合并的节点个数,本来最多要 \(n\) 条路径才能覆盖完,现在只有 \(n-\text{maxflow}\) 条,所以答案即为 \(n-\text{maxflow}\)。
考虑输入方案,就是每条增广路对应着一条流量为 \(1\) 的 \(u\to v+n\) 的边,此时 \(u\) 和 \(v\) 就可以合并,若又有一条 \(v\to w+n\) 的边,则 \(v\) 也可以和 \(w\) 合并,则 \(u\to v\to w\) 就是原图上一条路径。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 310, M = 1.5e4;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, m, nxt[N];
bool vis[N];
void solve()
{
cin >> n >> m;
int s = 0, t = n + n + 1;
rep(i, 1, n)
{
G.addEdge(s, i, 1);
G.addEdge(i + n, t, 1);
}
rep(i, 1, m)
{
int u, v;
cin >> u >> v;
G.addEdge(u, v + n, 1);
}
int res = G.maxflow(s, t);
rep(i, 1, n)
{
for (int j = G.head[i]; j; j = G.nxt[j])
{
int v = G.to[j];
if (!G.lim[j] && v > n && v <= n + n)
{
nxt[i] = v - n;
vis[v - n] = 1;
}
}
}
rep(i, 1, n)
{
if (!vis[i])
{
vis[i] = 1;
int u = i;
while (u)
{
cout << u << " ";
u = nxt[u];
}
cout << "\n";
}
}
cout << n - res << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
P2765 魔术球问题
网络流二十四题之一。
做完上一题,相信对这一题肯定有感觉了。没错,这道题只需要将和为完全平方数的两个数 \(x,y\) 中 \((x<y)\),连一条 \(x\to y\) 的边即可。下面有两种做法。
二分答案
二分答案是最无脑的做法了,每次二分答案直接建图,竟然不会超时!(甚至不用吸氧)
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 310, M = 1.5e4;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, m, nxt[N];
bool vis[N];
void solve()
{
cin >> n >> m;
int s = 0, t = n + n + 1;
rep(i, 1, n)
{
G.addEdge(s, i, 1);
G.addEdge(i + n, t, 1);
}
rep(i, 1, m)
{
int u, v;
cin >> u >> v;
G.addEdge(u, v + n, 1);
}
int res = G.maxflow(s, t);
rep(i, 1, n)
{
for (int j = G.head[i]; j; j = G.nxt[j])
{
int v = G.to[j];
if (!G.lim[j] && v > n && v <= n + n)
{
nxt[i] = v - n;
vis[v - n] = 1;
}
}
}
rep(i, 1, n)
{
if (!vis[i])
{
vis[i] = 1;
int u = i;
while (u)
{
cout << u << " ";
u = nxt[u];
}
cout << "\n";
}
}
cout << n - res << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
枚举答案
从小枚举答案的时候每次不把原来加的边删掉,而是直接加用有关当且枚举的答案的边,在上次跑完的残量网络上再跑最大流即可,感性理解一下是对的,注意每次最大流答案需要累加。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 3210, M = 3e5 + 10;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void init()
{
memset(head, 0, sizeof(head));
cnt = 1;
}
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, nxt[N], s = 0, t = 1;
bool ok[N];
void add_new(int x)
{
G.addEdge(s, (x << 1), 1);
G.addEdge((x << 1 | 1), t, 1);
for (int i = 1; i * i < x + x; i++)
{
if (i * i - x > 0)
{
G.addEdge(((i * i - x) << 1), (x << 1 | 1), 1);
}
}
}
void solve()
{
cin >> n;
int l = 0, sum = 0;
while (1)
{
add_new(++l);
int mf = G.maxflow(s, t);
sum += mf;
if (l - sum > n)
break;
}
l--;
cout << l << endl;
for (int u = 2; u <= l + l; u += 2)
{
for (int i = G.head[u]; i; i = G.nxt[i])
{
int v = G.to[i];
if (!G.lim[i] && (v & 1))
{
nxt[u >> 1] = (v >> 1);
ok[(v >> 1)] = 1;
}
}
}
rep(i, 1, l)
{
if (!ok[i])
{
ok[i] = 1;
int u = i;
while (u)
{
cout << u << " ";
u = nxt[u];
}
cout << endl;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
P2766 最长不下降子序列问题
网络流二十四题之一。
这道题用到了网络流的一个经典思想:分层建模。在接下来的建图中,你会体会到分层建模的巧妙之处。
首先,第一问直接暴力 DP 求解即可,设 \(f_i\) 表示以 \(a_i\) 结尾的最长不降子序列的长度,\(m\) 为第一题的答案。第二、三问考虑使用网络流处理。
第二问中,考虑到每个点最多只能被使用一次的条件不好在图中表示,所以将一个点拆成入点和出点两个点,连容量为 \(1\) 的边。例如, \(u\) 被拆成 \(u\) 和 \(u+n\) 两个点。
按 \(f_i\) 分层,具体来说:
- 若 \(f_i=1\),则 \(S\to i\) 连容量为 \(1\) 的边。
- 若 \(f_i=m\),则 \(i+n\to T\) 连容量为 \(1\) 的边。
- 若 \(i<j\) 且 \(a_i\leq a_j\) 且 \(f_j=f_i+1\)(这就是分层的思想),则 \(i+n\to j\) 连容量为 \(1\) 的边。
答案即为最大流。
借用一下 Troverld 大佬的图(图中拆点没有表现):
考虑第三问,相比第二问去掉了 \(1\) 和 \(n\) 只能用 \(1\) 次这个条件,那只需要加入如下几条容量都是 \(+\infin\) 的边即可:
- \(1\to 1+n\)
- \(n\to n+n\)
- \(S\to 1\)(\(f_1\) 必然为 \(1\))
- 若 \(f_n=m\),则 \(n+n\to T\)
答案也是最大流。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 1010, M = 250000;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void init()
{
memset(head, 0, sizeof(head));
cnt = 1;
}
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, a[510], dp[510];
void solve()
{
cin >> n;
if (n == 1)
{
cout << 1 << endl
<< 1 << endl
<< 1 << endl;
return;
}
int mx = 0;
rep(i, 1, n)
{
cin >> a[i];
rep(j, 0, i - 1)
{
if (a[j] <= a[i])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
mx = max(mx, dp[i]);
}
cout << mx << endl;
int s = 0, t = n + n + 1;
rep(i, 1, n)
{
G.addEdge(i, i + n, 1);
if (dp[i] == 1)
{
G.addEdge(s, i, 1);
}
if (dp[i] == mx)
{
G.addEdge(i + n, t, 1);
}
}
rep(i, 1, n)
{
rep(j, i + 1, n)
{
if (dp[j] == dp[i] + 1 && a[i] <= a[j])
{
G.addEdge(i + n, j, 1);
}
}
}
int res = G.maxflow(s, t);
cout << res << endl;
G.addEdge(1, 1 + n, INF);
G.addEdge(n, n + n, INF);
G.addEdge(s, 1, INF);
if (dp[n] == mx)
G.addEdge(n + n, t, INF);
cout << res + G.maxflow(s, t) << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
P2774 方格取数问题
网络流二十四题之一。
经典题,用到了一个方格图网络流建模的重要思想:黑白染色。
对网格黑白染色,即 \((i+j)\) 为奇数的然黑色,为偶数的染白色。
则源点向黑点连黑点权值容量的边,黑点向相邻白点连容量为 \(+\infin\) 的边,白点向汇点连白点权值容量的边。
则答案为原来所有点权和减去最小割。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 1e4 + 10, M = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void init()
{
memset(head, 0, sizeof(head));
cnt = 1;
}
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, m, a[110][110];
int getid(int x, int y)
{
return (x - 1) * m + y;
}
void solve()
{
cin >> n >> m;
int s = 0, t = n * m + 1, sum = 0;
rep(i, 1, n)
{
rep(j, 1, m)
{
cin >> a[i][j];
sum += a[i][j];
}
}
rep(i, 1, n)
{
rep(j, 1, m)
{
if ((i + j) & 1)
{
G.addEdge(s, getid(i, j), a[i][j]);
rep(k, 0, 3)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 1 && x <= n && y >= 1 && y <= m)
{
G.addEdge(getid(i, j), getid(x, y), INF);
}
}
}
else
{
G.addEdge(getid(i, j), t, a[i][j]);
}
}
}
cout << sum - G.maxflow(s, t) << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
P2805 [NOI2009] 植物大战僵尸
先引入一个概念:闭合子图。
闭合子图就是说在一个有向图里,如果从一个子图中的所有点开始 DFS,到达的所有点也是这个子图里的,则称该子图为闭合子图。
显然,如果把原题中的保护关系(包括同一行的先后关系也算)反向建边(即被保护的点向保护它的点连边),则我们要求的就是一个 最大权闭合子图。因为如果有一个点能到达子图外的其他点,则这个点肯定不能被吃掉。
下面考虑如何求最大权闭合子图。用网络流解决这个问题,把所有正权点都连向源点,负权点都连向汇点,边权就是当前点权值的绝对值,然后保护关系反向连边,容量为 \(+\infin\)。
则答案为所有正权点的权值和减掉最小割。
不是很会证明,但是可以感性理解一下(下面是瞎扯,可以略过):首先断掉的一定要么是和源点连边要么和汇点连边,如果断的是前者,则代表放弃一个正权点;如果断的是后者,则代表选择一个负权点(因为最后会拿正权点的权值减掉它,相当于贡献是负的)。建议自己画个图就能理解了。
这样问题就解决了,但是如果写出来会 WA,原因是如果出现环是不可能取到环上的点的,所以需要把环扔掉,即做一遍拓扑排序。这样就可以通过了。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 600 + 10, M = 1e6 + 10;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void init()
{
memset(head, 0, sizeof(head));
cnt = 1;
}
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int n, m, a[22][33], deg[22][33];
queue<pii> q;
vector<pii> g[22][33];
int getid(int x, int y)
{
return x * m + y + 1;
}
void solve()
{
cin >> n >> m;
int s = 0, t = n * m + 1;
rep(i, 0, n - 1)
{
rep(j, 0, m - 1)
{
cin >> a[i][j];
int k;
cin >> k;
while (k--)
{
int x, y;
cin >> x >> y;
G.addEdge(getid(x, y), getid(i, j), INF);
g[i][j].push_back({x, y});
deg[x][y]++;
}
}
}
rep(i, 0, n - 1)
{
rep(j, 0, m - 2)
{
G.addEdge(getid(i, j), getid(i, j + 1), INF);
g[i][j + 1].push_back({i, j});
deg[i][j]++;
}
}
rep(i, 0, n - 1)
{
rep(j, 0, m - 1)
{
if (!deg[i][j])
{
q.push({i, j});
}
}
}
int sum = 0;
while (!q.empty())
{
int x = q.front().first, y = q.front().second;
q.pop();
if (a[x][y] >= 0)
{
G.addEdge(s, getid(x, y), a[x][y]);
sum += a[x][y];
}
else
{
G.addEdge(getid(x, y), t, -a[x][y]);
}
for (auto [tx, ty] : g[x][y])
{
deg[tx][ty]--;
if (!deg[tx][ty])
{
q.push({tx, ty});
}
}
}
cout << sum - G.maxflow(s, t) << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
网络流二十四题之一。
简单贪心题用费用流,哦耶!
好像是第一道费用流耶,比较基础。设 \(A\) 表示 \(a_1,a_2,\cdots,a_n\) 的平均数,则建图方式如下:
- 若 \(a_i\geq A\),则 \(s\to i\) 连一条容量为 \(a_i-A\),费用为 \(0\) 的边。
- 若 \(a_i<A\),则 \(i\to t\) 连一条容量为 \(A-a_i\),费用为 \(0\) 的边。
- \(i\to i+1\),\(i\to i-1\) 连容量为 \(+\infin\),费用为 \(0\) 的边。
跑费用流,费用即为答案。(最大流限制了平均,费用流保证了最小)
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 110, M = 810;
const int INF = 0x3f3f3f3f;
struct MCMF
{
int head[N], nxt[M], to[M], lim[M], cst[M], cnt = 1;
void addEdge(int u, int v, int w, int c)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w, cst[cnt] = c;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0, cst[cnt] = -c;
}
int fr[N], fl[N], dis[N];
bool vis[N];
pair<int, int> mcmf(int s, int t)
{
int flow = 0, cost = 0;
while (1)
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
fl[s] = INF;
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i], d = dis[u] + cst[i];
if (lim[i] && dis[v] > d)
{
dis[v] = d;
fr[v] = i;
fl[v] = min(lim[i], fl[u]);
if (!vis[v])
{
vis[v] = 1;
q.push(v);
}
}
}
}
if (dis[t] > 1e9)
return {flow, cost};
flow += fl[t], cost += dis[t] * fl[t];
for (int u = t; u != s; u = to[fr[u] ^ 1])
{
lim[fr[u]] -= fl[t];
lim[fr[u] ^ 1] += fl[t];
}
}
}
} G;
int n, a[N];
void solve()
{
cin >> n;
int s = 0, t = n + 1;
int sum = 0;
rep(i, 1, n)
{
cin >> a[i];
sum += a[i];
}
int ave = sum / n;
rep(i, 1, n)
{
if (a[i] >= ave)
G.addEdge(s, i, a[i] - ave, 0);
else
G.addEdge(i, t, ave - a[i], 0);
G.addEdge(i, i % n + 1, INF, 1);
G.addEdge(i, (i - 2 + n) % n + 1, INF, 1);
}
cout << G.mcmf(s, t).second << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
P3254 圆桌问题
直接暴力建图即可。
源点向每个单位连容量为 \(r_i\) 的边,单位向每个桌子连容量为 \(1\) 的边,桌子向汇点连容量为 \(c_i\) 的边。
直接跑最大流即可。
点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
if (y > x)
x = y;
}
template <typename T>
void chmin(T &x, int y)
{
if (y < x)
x = y;
}
using namespace std;
void setIO(string name)
{
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 450, M = 1e5;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
int head[N], nxt[M], to[M], cnt = 1;
int lim[M];
void init()
{
memset(head, 0, sizeof(head));
cnt = 1;
}
void addEdge(int u, int v, int w)
{
nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
};
int T, cur[N], dis[N];
int dfs(int u, int res)
{
if (u == T)
return res;
int flow = 0;
for (int i = cur[u]; i && res; i = nxt[i])
{
cur[u] = i;
int v = to[i];
int c = min(lim[i], res);
if (c && dis[v] == dis[u] + 1)
{
int f = dfs(v, c);
res -= f;
flow += f;
lim[i] -= f;
lim[i ^ 1] += f;
}
}
return flow;
}
int maxflow(int s, int t)
{
T = t;
int flow = 0;
while (1)
{
queue<int> q;
memset(dis, -1, sizeof(dis));
memcpy(cur, head, sizeof(head));
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dis[v] == -1 && lim[i])
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if (dis[t] == -1)
return flow;
flow += dfs(s, INF);
}
}
} G;
int m, n, r[160], c[280];
void solve()
{
cin >> m >> n;
int s = 0, t = m + n + 1;
int sum = 0;
rep(i, 1, m)
{
cin >> r[i];
sum += r[i];
G.addEdge(s, i, r[i]);
}
rep(i, 1, n)
{
cin >> c[i];
G.addEdge(i + m, t, c[i]);
}
rep(i, 1, m)
{
rep(j, 1, n)
{
G.addEdge(i, j + m, 1);
}
}
int mf = G.maxflow(s, t);
if (mf < sum)
{
cout << "0" << endl;
return;
}
cout << "1" << endl;
rep(i, 1, m)
{
for (int j = G.head[i]; j; j = G.nxt[j])
{
if (!G.lim[j])
{
cout << G.to[j] - m << " ";
}
}
cout << endl;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
本文来自博客园,作者:Jerry_Jiang,转载请注明原文链接:https://www.cnblogs.com/Jerry-Jiang/p/17803791.html