1.最大流
POJ1459 - Power Network
这道题看上去很复杂,但实际上就是一个非常简单的最大流模型。我们设一个超级源点 s s s,超级汇点 t t t。从 s s s向每一个power station连接一个边权为 p [ u ] p[u] p[u]的有向边( u u u为这个power station的编号)。然后将所有consumer向 t t t连接一条边权为无穷大的有向边。中间的每一条dispatcher,都对应一条边。然后跑一个最大流即可,得到的最大流的值就是这道题目的答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
const int N = 200005;
const int M = 400005;
int n, m, s, t, nc, np, en;
int front[N];
struct Edge {
LL v, w, next;
}e[100050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[200005], gap[200005];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
void main2() {
en = 1;
cin >> np >> nc >> m;
isap.node_cnt = n + 2;
s = n + 1; t = n + 2;
for (int i = 1; i <= n + 2; ++i) {
front[i] = 0;
}
for (int i = 1; i <= m; ++i) {
string x; cin >> x;
int len = x.length();
LL u, v, w, tmp = 0;
for (int j = 0; j < len; ++j) {
if (x[j] == '(') continue;
else if (x[j] == ',') {
u = tmp + 1;
tmp = 0;
}
else if (x[j] == ')') {
v = tmp + 1;
tmp = 0;
}
else tmp = tmp * 10ll + x[j] - '0';
}
w = tmp;
addEdge(u, v, w);
addEdge(v, u, 0ll);
}
for (int i = 1; i <= np; ++i) {
string x; cin >> x;
int len = x.length();
LL v, w, tmp = 0;
for (int j = 0; j < len; ++j) {
if (x[j] == '(') continue;
else if (x[j] == ')') {
v = tmp + 1;
tmp = 0;
}
else tmp = tmp * 10ll + x[j] - '0';
}
w = tmp;
addEdge(s, v, w);
addEdge(v, s, 0ll);
}
for (int i = 1; i <= nc; ++i) {
string x; cin >> x;
int len = x.length();
LL u, w, tmp = 0;
for (int j = 0; j < len; ++j) {
if (x[j] == '(') continue;
else if (x[j] == ')') {
u = tmp + 1;
tmp = 0;
}
else tmp = tmp * 10ll + x[j] - '0';
}
w = tmp;
addEdge(u, t, w);
addEdge(t, u, 0ll);
}
isap.opt();
cout << isap.maxflow << '\n';
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (cin >> n) main2();
return 0;
}
HDU4280 - Island Transport
基础的最大流板子题。
根据读入的所有点坐标,将这些点按照横坐标进行排序。这样就可以确定 s s s点和 t t t点了。然后剩下的就是读入每一条路径,将每一条路径 ( u , v ) (u,v) (u,v)分别建立 u → v u\rightarrow v u→v, v → u v\rightarrow u v→u两条正边以及其对应的反边,然后跑一个最大流即可。
最大流的值即为答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
int n, m, s, t, en;
int front[100005];
struct Edge {
LL v, w, next;
}e[1000050];
void addEdge(LL u, LL v, LL w) {
e[++en] = {
v, w, front[u]};
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100050], gap[100050];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
node_cnt = n;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
struct POINT {
int x, y, id;
}a[100005];
void main2() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
front[i] = 0;
}
en = 1;
for (int i = 1; i <= n; ++i) {
cin >> a[i].x >> a[i].y;
a[i].id = i;
}
sort(a + 1, a + n + 1, [](const POINT &A, const POINT &B) {
return (A.x < B.x);
});
s = a[1].id; t = a[n].id;
for (int i = 1; i <= m; ++i) {
int x, y, z;
cin >> x >> y >> z;
addEdge(x, y, z);
addEdge(y, x, 0);
addEdge(y, x, z);
addEdge(x, y, 0);
}
isap.opt();
cout << isap.maxflow << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
while (_--) main2();
return 0;
}
POJ3436 - ACM Computer Factory
每一个点的始末状态都不相同,所以我们要将每一个点都进行拆点,即用 i i i表示第 i i i个点的初始状态,用 i + n i+n i+n表示第 i i i个点的终止状态。
接下来我们根据题目中的过程描述进行连边。
连的边有两种:
- 不同点之间的关联。如果 u + n , v u+n,v u+n,v两个点之间应当连一条有向边,当且仅当 u u u的终止状态可以被 v v v的初始状态所接受。
- 同一个点从初始状态到终止状态的过程转移。
不难想到,连接的所有边的边权都是 1 1 1。
题目要求是求出单位时间内的最大产能即最大产能的方案,其实就是求这个网络的最大流和构成最大流的流的构成。
求解最大流的值得算法有很多,有dinic,有ISAP等,直接套板子即可。
在求完网络流后求解最大流的方案的方法是:遍历残量网络,如果所连的反边经过的流大于 0 0 0,则意味着这条反边对应的正向边被选中,且反边的权值即为正边所流过的流量。
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
int p, n, en, s, t;
int front[100005], per[100005];
vector<int> st[100005], ed[100005];
struct TUPLE {
int x, y, z;
};
struct Edge {
LL u, v, w, next;
}e[100050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].u = u;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[550], gap[550];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
node_cnt = n * 2 + 2;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
void main2() {
cin >> n;
for (int i = 1; i <= n; ++i) {
st[i].clear();
ed[i].clear();
}
for (int i = 1; i <= n * 2 + 2; ++i) {
front[i] = per[i] = 0;
}
en = 1;
t = 2 * n + 2;
s = 2 * n + 1;
for (int i = 1; i <= n; ++i) {
cin >> per[i];
int all = 1;
for (int j = 1; j <= p; ++j) {
int x; cin >> x;
st[i].push_back(x);
if (x == 1) all = 0;
}
if (all) {
addEdge(s, i, INF);
addEdge(i, s, 0);
}
all = 1;
for (int j = 1; j <= p; ++j) {
int x; cin >> x;
ed[i].push_back(x);
if (x == 0) all = 0;
}
if (all) {
addEdge(i + n, t, INF);
addEdge(t, i + n, 0);
}
}
int cnt2 = en;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i == j) continue;
int ok = 1;
for (int k = 0; k < p; ++k) {
if (st[j][k] == 2) continue;
if (st[j][k] != ed[i][k]) {
ok = 0;
break;
}
}
if (ok) {
addEdge(i + n, j, min(per[i], per[j]));
addEdge(j, i + n, 0);
}
}
}
for (int i = 1; i <= n; ++i) {
addEdge(i, i + n, per[i]);
addEdge(i + n, i, 0);
}
isap.opt();
int ecnt = 0;
vector<TUPLE> ans;
for (int i = cnt2 + 1; i <= en; ++i) {
int u = e[i].u, v = e[i].v, w = e[i].w;
if (v > n and w > 0 and abs(u - v) != n) {
++ecnt;
TUPLE tmp;
tmp.x = v - n;
tmp.y = u;
tmp.z = w;
ans.push_back(tmp);
}
}
cout << isap.maxflow << ' ' << ecnt << '\n';
for (int i = 0; i < ans.size(); ++i) {
cout << ans[i].x << ' ' << ans[i].y << ' ' << ans[i].z << '\n';
}
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (cin >> p) main2();
return 0;
}
POJ3281 - Dining
考查建图。建立一个超级源点 s s s,建立一个超级汇点 t t t。
考虑到每一头牛都只能喝到一种饮料,所以我们可以先让牛匹配食物,然后再匹配饮料。
首先,从超级源点向所有的食物点连一条有向边,边权为 1 1 1。
然后进行食物与牛的匹配,根据牛的喜好,对于每一头牛,从其所有喜欢的食物的食物点到这头牛连一条边权为 1 1 1的有向边。
此时可能会存在牛的点的入度大于 1 1 1的情况,所以对牛进行拆点控制流量,即每一头牛用两个点来表示,这两个点之间连接一条边权为 1 1 1的有向边。对于这两个点,为了方便后面描述,描述为牛的入点指向出点。
然后匹配牛和饮料,对于每一头牛,我们从这头牛的出点向所有其喜欢的饮料连接一条边权为 1 1 1的有向边。
最后将所有的饮料点向 t t t连接一条边权为 1 1 1的有向边。
可以发现我们这次的建图,从每一个饮料点,如果有流量从这个点指向 t t t,说明这个饮料被一个有食物吃的牛匹配了。那么超级汇点 t t t得到的流量数就是牛、食物、饮料的匹配数。
所以对于这张建好的图跑一个最大流就是答案。
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
LL n, f, dr, en, s, t;
LL front[10005];
vector<LL> food[105], drink[105];
struct Edge {
LL v, w, next;
}e[40005];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100050], gap[100050];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
node_cnt = n * 2 + f + dr + 2;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
void main2() {
cin >> n >> f >> dr;
s = n * 2 + f + dr + 1;
t = n * 2 + f + dr + 2;
en = 1;
for (int i = 1; i <= t; ++i) {
front[i] = 0;
}
for (int i = 1; i <= n; ++i) {
food[i].clear();
drink[i].clear();
}
for (int i = 1; i <= f; ++i) {
addEdge(s, i, 1);
addEdge(i, s, 0);
}
for (int i = 1; i <= n; ++i) {
int x, y;
cin >> x >> y;
for (int j = 1; j <= x; ++j) {
int z; cin >> z;
food[i].push_back(z);
}
for (int j = 1; j <= y; ++j) {
int z; cin >> z;
drink[i].push_back(z);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < food[i].size(); ++j) {
addEdge(food[i][j], i + f, 1);
addEdge(i + f, food[i][j], 0);
}
}
for (int i = 1; i <= n; ++i) {
addEdge(i + f, i + n + f, 1);
addEdge(i + n + f, i + f, 0);
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < drink[i].size(); ++j) {
addEdge(i + n + f, n * 2 + f + drink[i][j], 1);
addEdge(n * 2 + f + drink[i][j], i + n + f, 0);
}
}
for (int i = 1; i <= dr; ++i) {
addEdge(n * 2 + f + i, t, 1);
addEdge(t, n * 2 + f + i, 0);
}
isap.opt();
cout << isap.maxflow << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
HDU4292 - Food
这道题和POJ3281几乎一样,只不过饮料和食物的数量是通过输入给进来的,我们只需要根据输入的食物和饮料的数量改变一下从超级源点 s s s到食物、从饮料到超级汇点 t t t的边权即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e14;
LL front[100005];
LL en = 1;
LL n, m, f, d, s, t;
struct Edge {
LL v, w, next;
}e[400050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100005], gap[100005];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
vector<int> F, D;
void main2() {
cin >> f >> d;
isap.node_cnt = n * 2 + f + d + 2;
t = isap.node_cnt;
s = t - 1;
for (int i = 1; i <= t; ++i) {
front[i] = 0;
}
en = 1;
F.clear(); D.clear();
for (int i = 1; i <= f; ++i) {
int x; cin >> x;
F.push_back(x);
}
for (int i = 1; i <= d; ++i) {
int x; cin >> x;
D.push_back(x);
}
for (int i = 1; i <= f; ++i) {
addEdge(s, i, F[i - 1]);
addEdge(i, s, 0);
}
for (int i = 1; i <= n; ++i) {
string x;
cin >> x;
for (int j = 0; j < f; ++j) {
if (x[j] == 'Y') {
addEdge(j + 1, f + i, 1);
addEdge(f + i, j + 1, 0);
}
}
}
for (int i = 1; i <= n; ++i) {
addEdge(i + f, i + n + f, 1);
addEdge(i + n + f, i + f, 0);
}
for (int i = 1; i <= n; ++i) {
string x;
cin >> x;
for (int j = 0; j < d; ++j) {
if (x[j] == 'Y') {
addEdge(i + n + f, j + 1 + n * 2 + f, 1);
addEdge(j + 1 + n * 2 + f, i + n + f, 0);
}
}
}
for (int i = 1; i <= d; ++i) {
addEdge(i + n * 2 + f, t, D[i - 1]);
addEdge(t, i + n * 2 + f, 0);
}
isap.opt();
cout << isap.maxflow << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (cin >> n) main2();
return 0;
}
POJ1087 - A Plug for UNIX
初始限制条件是前面给的所有的插座各 1 1 1个,所以我们将所有种类的插座排成一列放在超级源点 s s s的后面,然后从 s s s向所有有的插座连一条边权为 1 1 1的有向边。
然后考虑适配器,每有一个适配器都意味着 A , B A,B A,B之间的转化,即从 A A A到 B B B连接一条边权为 I N F INF INF的有向边。
最后考虑需要使用插座的插头。我们要求最多有多少有插座用的插头,那么就将所有插头点向超级汇点连接一条边权为 1 1 1的有向边,这样求得最大流就是答案。然后将每个插头对应的插座向这个插头连接一条边权为 I N F INF INF的有向边。
最后跑一个网络流即可。
#include<iostream>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
LL n, m, k, s, t, cnt, en;
LL front[100005];
struct Edge {
LL v, w, next;
}e[100050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100050], gap[100050];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
node_cnt = cnt;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
int ss[100005], mps[100005];
int si;
map<LL, int> mp;
LL calc(string &x) {
LL hsh = 0;
int len = x.length();
for (int i = 0; i < len; ++i) {
hsh = hsh * 31ll + x[i];
}
return hsh;
}
vector<int> sa, sb;
vector<pair<int, int> > sc;
void main2() {
si = 0; en = 1; cnt = 0;
cin >> n;
for (int i = 1; i <= n; ++i) {
string x;
cin >> x;
LL hsh = calc(x);
if (!mp[hsh]) {
ss[++si] = hsh;
mp[hsh] = si;
}
sa.push_back(mp[hsh]);
}
cin >> m;
for (int i = 1; i <= m; ++i) {
string x;
cin >> x;
cin >> x;
LL hsh = calc(x);
if (!mp[hsh]) {
ss[++si] = hsh;
mp[hsh] = si;
}
sb.push_back(mp[hsh]);
}
cin >> k;
for (int i = 1; i <= k; ++i) {
string x, y;
cin >> x >> y;
LL hsh;
int tmpx, tmpy;
hsh = calc(x);
if (!mp[hsh]) {
ss[++si] = hsh;
mp[hsh] = si;
}
tmpx = mp[hsh];
hsh = calc(y);
if (!mp[hsh]) {
ss[++si] = hsh;
mp[hsh] = si;
}
tmpy = mp[hsh];
sc.push_back(make_pair(tmpx, tmpy));
}
s = si * 2 + 1;
t = cnt = si * 2 + 2;
for (int i = 1; i <= si; ++i) {
mps[i] = 0;
}
for (int i = 0; i < sb.size(); ++i) {
++mps[sb[i]];
}
for (int i = 1; i <= si; ++i) {
if (mps[i] > 0) {
addEdge(s, i, mps[i]);
addEdge(i, s, 0);
}
}
for (int i = 1; i <= si; ++i) {
mps[i] = 0;
}
for (int i = 0; i < sa.size(); ++i) {
++mps[sa[i]];
}
for (int i = 1; i <= si; ++i) {
if (mps[i] > 0) {
addEdge(i + si, t, mps[i]);
addEdge(t, i + si, 0);
}
}
for (int i = 0; i < sc.size(); ++i) {
int x = sc[i].first + si, y = sc[i].second + si;
addEdge(x, y, INF);
addEdge(y, x, 0);
}
for (int i = 1; i <= si; ++i) {
addEdge(i, i + si, INF);
addEdge(i + si, i, 0);
}
isap.opt();
cout << m - isap.maxflow << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
HDU2732 - Leapin’ Lizards
求最少的没有跳出网格的蜥蜴,等同于求总共的蜥蜴数量减去最多跳出网格的数量。考虑网络流求最大数量。
由于网格不大,所以我们可以考虑将网格的所有点都当做图的一个点。
首先,将所有蜥蜴的初始位置的点找出来,从超级源点 s s s向这些点连一条边权为 1 1 1的有向边。
题目中高度的含义是指,经过这个点的次数。换句话说,我们可以把这个量当做经过这个点的容量,点权通过拆点化为边权,即将网格上的每一个点都拆成两个点,中间用一条边权为这个点的高度的有向边连接。
然后考虑每个点跳出网格的过程。我们枚举网格上的每一个点,并枚举每一个点的所有和这个点的曼哈顿距离小于等于 d d d的点。如果找到的点不在网格上,那么将终点设为超级汇点 t t t,表征从这个点跳可以跳出网格。否则那个点就是终点,于是从这个点像找到的每一个终点连一条边权为无穷大的有向边。这里之所以边权设置为无穷大,是因为我们不知道从这里跳出网格外的蜥蜴有多少只,我们也不需要在这里加以限制,所以直接无穷大即可。
最后建好的图,跑一个从超级源点 s s s到超级汇点 t t t的最大流即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
LL front[100005];
LL en = 1;
LL n, m, s, t, cnt, d;
struct Edge {
LL v, w, next;
}e[200050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100005], gap[100005];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
int h[25][25];
int id(int x, int y, int in_node) {
int ret = (x - 1) * m + y;
if (in_node == 0) ret += n * m;
return ret;
}
void ADD(int x, int y) {
int cur = id(x, y, 0);
for (int i = -d; i <= d; ++i) {
int step = d - abs(i);
for (int j = -step; j <= step; ++j) {
int xx = x + i, yy, tar;
yy = y - j;
tar = id(xx, yy, 1);
if (xx < 1 or xx > n or yy < 1 or yy > m) {
tar = t;
}
addEdge(cur, tar, INF);
addEdge(tar, cur, 0);
yy = y + j;
tar = id(xx, yy, 1);
if (xx < 1 or xx > n or yy < 1 or yy > m) {
tar = t;
}
addEdge(cur, tar, INF);
addEdge(tar, cur, 0);
}
}
}
int main2() {
cin >> n >> d;
cnt = 0;
en = 1;
for (int i = 1; i <= n; ++i) {
string x;
cin >> x;
m = x.length();
for (int j = 0; j < m; ++j) {
h[i][j + 1] = x[j] - '0';
}
}
for (int i = 1; i <= n * m * 2 + 2; ++i) {
front[i] = 0;
}
isap.node_cnt = n * m * 2 + 2;
s = n * m * 2 + 1;
t = s + 1;
for (int i = 1; i <= n; ++i) {
string x;
cin >> x;
for (int j = 0; j < m; ++j) {
if (x[j] == 'L') {
addEdge(s, id(i, j + 1, 1), 1);
addEdge(id(i, j + 1, 1), s, 0);
++cnt;
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
ADD(i, j);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (h[i][j] == 0) continue;
addEdge(id(i, j, 1), id(i, j, 0), h[i][j]);
addEdge(id(i, j, 0), id(i, j, 1), 0);
}
}
isap.opt();
// cout << cnt - isap.maxflow << '\n';
return (cnt - isap.maxflow);
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
for (int i = 1; i <= _; ++i) {
cout << "Case #" << i << ": ";
int ans = main2();
if (ans == 0) cout << "no ";
else cout << ans << ' ';
if (ans < 2) cout << "lizard was ";
else cout << "lizards were ";
cout << "left behind.\n";
}
return 0;
}
2.最小费用最大流
POJ2195 - Going Home
由于人和房子的数量一定,而且数量非常小,根据每一个人都有可能去向所有的房子,所以我们 n 2 n^2 n2把所有的人和所有的房子连在一起,从人到房子连接一条带有距离为费用的、边权为 1 1 1的有向边。
然后我们从超级源点向所有的人连接一条费用为 0 0 0,边权为无穷大的有向边。
然后我们从所有房子向超级汇点连接一条费用为 0 0 0,边权为无穷大的有向边。
然后跑一个最小费用最大流,得到的最小费用就是答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
const int N = 5005;
const int M = 50005;
int s, t, en;
int front[N], vis[N];
struct Edge {
LL v, w, c, next;
}e[M * 4];
void addEdge(LL u, LL v, LL w, LL c) {
++en;
e[en].v = v;
e[en].w = w;
e[en].c = c;
e[en].next = front[u];
front[u] = en;
}
struct MCMF {
LL dis[N], cur[N];
LL node_cnt, maxflow, mincost;
bool spfa(int _s, int _t) {
for (int i = 1; i <= node_cnt; ++i) {
dis[i] = INF;
cur[i] = front[i];
}
queue<LL> q;
q.push(_s); dis[_s] = 0; vis[_s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v, w = e[i].w, c = e[i].c;
if (w and dis[v] > dis[u] + c) {
dis[v] = dis[u] + c;
if (!vis[v]) {
q.push(v); vis[v] = 1;
}
}
}
}
return (dis[_t] != INF);
}
LL dfs(LL u, LL _t, LL flow) {
if (u == _t) return flow;
vis[u] = 1;
LL ans = 0;
for (LL &i = cur[u]; i and ans < flow; i = e[i].next) {
LL v = e[i].v, w = e[i].w, c = e[i].c;
if (!vis[v] and w and dis[v] == dis[u] + c) {
LL x = dfs(v, _t, min(w, flow - ans));
if (x) {
mincost += x * c;
e[i].w -= x;
e[i ^ 1].w += x;
ans += x;
}
}
}
vis[u] = 0;
return ans;
}
void opt(int _s, int _t) {
maxflow = mincost = 0;
while (spfa(_s, _t)) {
int x;
while ((x = dfs(_s, _t, INF))) {
maxflow += x;
}
}
}
}fl;
int n, m;
vector<pair<int, int> > per, hou;
void main2() {
per.clear();
hou.clear();
en = 1;
for (int i = 1; i <= n; ++i) {
string s;
cin >> s;
for (int j = 0; j < m; ++j) {
if (s[j] == 'm') per.push_back(make_pair(i, j + 1));
if (s[j] == 'H') hou.push_back(make_pair(i, j + 1));
}
}
fl.node_cnt = per.size() + hou.size() + 2;
s = fl.node_cnt - 1;
t = fl.node_cnt;
for (int i = 0; i < per.size(); ++i) {
for (int j = 0; j < hou.size(); ++j) {
int x1 = per[i].first, y1 = per[i].second;
int x2 = hou[j].first, y2 = hou[j].second;
LL d = abs(x1 - x2) + abs(y1 - y2);
addEdge(i + 1, j + 1 + per.size(), 1ll, d);
addEdge(j + 1 + per.size(), i + 1, 0ll, -d);
}
}
for (int i = 0; i < per.size(); ++i) {
addEdge(s, i + 1, 1ll, 0ll);
addEdge(i + 1, s, 0ll, 0ll);
}
for (int i = 0; i < hou.size(); ++i) {
addEdge(i + per.size() + 1, t, 1ll, 0ll);
addEdge(t, i + per.size() + 1, 0ll, 0ll);
}
fl.opt(s, t);
cout << fl.mincost << '\n';
for (int i = 1; i <= fl.node_cnt; ++i) {
front[i] = 0;
}
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (1) {
cin >> n >> m;
if (!n and !m) break;
main2();
}
return 0;
}
POJ2516 - Minimum Cost
由于费用是按照每一个货物计算的,所以如果按照我们之前的建边方式,一条边考虑一次性运输多个货物,那么此时这条边上的费用在最大流情况中如果没有留满,就会导致费用计算错误。
既然一个物品对应一个费用,发现每一个货物的量最多是 3 3 3,所以我们可以考虑对每一个货物进行建边。那么根据题目的意思,给每一个货物进行建边。从一个供应商到一个卖家,要连接这样的边的数量,可以认为是供应商可以提供的这个商品的数量。就算供应商可以提供的数量大于卖家所需要的数量,那么可以在卖家的后面再加上卖家所需要的量的限制。
接下来的问题是,商品不止一种。如果我们将所有的商品都建到一个图里面,那么这个图会变得相当复杂。我们发现,每一种货物之间对于答案的贡献是互不影响的,所以我们可以对于每一种货物单独建图,单独跑最小费用最大流,然后将每一种货物跑出来的最小花费加到一起,就是这道题目的答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
const int N = 5005;
const int M = 50005;
LL n, m, k, en, ans = 0, s, t;
LL front[N], vis[N];
LL cst[55][55][55];
struct Edge {
LL v, w, c, next;
}e[M * 4];
void addEdge(LL u, LL v, LL w, LL c) {
++en;
e[en].v = v;
e[en].w = w;
e[en].c = c;
e[en].next = front[u];
front[u] = en;
}
struct MCMF {
LL dis[N], cur[N];
LL node_cnt, maxflow, mincost;
bool spfa(int _s, int _t) {
for (int i = 1; i <= node_cnt; ++i) {
dis[i] = INF;
cur[i] = front[i];
}
queue<LL> q;
q.push(_s); dis[_s] = 0; vis[_s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v, w = e[i].w, c = e[i].c;
if (w and dis[v] > dis[u] + c) {
dis[v] = dis[u] + c;
if (!vis[v]) {
q.push(v); vis[v] = 1;
}
}
}
}
return (dis[_t] != INF);
}
LL dfs(LL u, LL _t, LL flow) {
if (u == _t) return flow;
vis[u] = 1;
LL ans = 0;
for (LL &i = cur[u]; i and ans < flow; i = e[i].next) {
LL v = e[i].v, w = e[i].w, c = e[i].c;
if (!vis[v] and w and dis[v] == dis[u] + c) {
LL x = dfs(v, _t, min(w, flow - ans));
if (x) {
mincost += x * c;
e[i].w -= x;
e[i ^ 1].w += x;
ans += x;
}
}
}
vis[u] = 0;
return ans;
}
void opt(int _s, int _t) {
maxflow = mincost = 0;
while (spfa(_s, _t)) {
int x;
while ((x = dfs(_s, _t, INF))) {
maxflow += x;
}
}
}
}fl;
vector<LL> ord[55], ned[55];
void main2() {
ans = 0;
for (int i = 1; i <= n; ++i) {
ned[i].clear();
for (int j = 1; j <= k; ++j) {
int x; cin >> x;
ned[i].push_back(x);
}
}
for (int i = 1; i <= m; ++i) {
ord[i].clear();
for (int j = 1; j <= k; ++j) {
int x; cin >> x;
ord[i].push_back(x);
}
}
for (int x = 1; x <= k; ++x) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> cst[x][j][i];
}
}
}
fl.node_cnt = n + m + 2;
t = fl.node_cnt;
s = t - 1;
for (int K = 1; K <= k; ++K) {
en = 1;
for (int i = 1; i <= fl.node_cnt; ++i) {
front[i] = 0;
}
for (int i = 1; i <= m; ++i) {
if (ord[i][K - 1] == 0) continue;
for (int j = 1; j <= n; ++j) {
if (ned[j][K - 1] == 0) continue;
for (int x = 1; x <= ord[i][K - 1]; ++x) {
addEdge(i, j + m, 1ll, cst[K][i][j]);
addEdge(j + m, i, 0ll, -cst[K][i][j]);
}
}
}
for (int i = 1; i <= m; ++i) {
addEdge(s, i, ord[i][K - 1], 0ll);
addEdge(i, s, 0ll, 0ll);
}
for (int i = 1; i <= n; ++i) {
addEdge(i + m, t, ned[i][K - 1], 0ll);
addEdge(t, i + m, 0ll, 0ll);
}
LL tar = 0;
for (int i = 1; i <= n; ++i) {
tar += ned[i][K - 1];
}
fl.opt(s, t);
if (fl.maxflow < tar) {
cout << -1 << '\n';
return;
}
ans += fl.mincost;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (1) {
cin >> n >> m >> k;
if (!n and !m and !k) break;
// cout << "n = " << n << " m = " << m << " k = " << k << '\n';
main2();
}
return 0;
}
3.最小割
HDU4289 - Control
已知起点和终点,要求用最小的费用将图变为起点与终点不连通,这显然是求最小割。然而我们的代价在点上,这个时候我们没法使用网络流,需要将整张图变个样子。
我们采用拆点的方式,将第 i i i个点变为由 i i i号点和 i + n i+n i+n号点组成的点对,并从 i i i号点向 i + n i+n i+n号点连接一条边权为这个点的代价的有向边。
接下来所有的边 ( u , v ) (u,v) (u,v),我们都建立 u + n u+n u+n和 v v v之间的边。双向边的另一条边同理。其他的边权为 I N F INF INF。
根据最大流=最小割,我们跑一个最大流,即可求出这道题目的答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
LL front[100050], a[100050];
LL en = 1;
LL n, m, s, t;
struct Edge {
LL v, w, next;
}e[100050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100050], gap[100050];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
int ss, tt;
void main2() {
cin >> m;
isap.node_cnt = n * 2 + 2;
s = n * 2 + 1; t = s + 1;
en = 1;
cin >> ss >> tt;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n * 2 + 2; ++i) {
front[i] = 0;
}
addEdge(s, ss, INF);
addEdge(ss, s, 0);
addEdge(tt + n, t, INF);
addEdge(t, tt + n, 0);
for (int i = 1; i <= n; ++i) {
addEdge(i, i + n, a[i]);
addEdge(i + n, i, 0);
}
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
addEdge(x + n, y, INF);
addEdge(y, x + n, 0);
addEdge(y + n, x, INF);
addEdge(x, y + n, 0);
}
isap.opt();
cout << isap.maxflow << '\n';
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (cin >> n) main2();
return 0;
}
UVA10480 - Sabotage
求最小割并输出方案。求最小割=求最大流,不多赘述。接下来考虑如何输出任意方案。
我们发现,最小割的结果是跑完最大流后的残量网络中起点和终点不连通。而在残量网络上我们可以通过dfs找到所有与起点相连的边。将这些点染色为黑色。剩下的跑不到的点染色为白色。接下来遍历所有的边,可以作为答案的一组边满足其两端的点所染的颜色不同。将这些边中不重复的、不互为正反边的边输出出来。这必然可以成为一种方案。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
LL front[100050], vis[100005];
LL en = 1;
LL n, m, s, t;
struct Edge {
LL u, v, w, next;
}e[100050];
void addEdge(LL u, LL v, LL w) {
++en;
e[en].u = u;
e[en].v = v;
e[en].w = w;
e[en].next = front[u];
front[u] = en;
}
struct ISAP {
LL maxflow, node_cnt;
LL dep[100050], gap[100050];
void bfs() {
for (LL i = 1; i <= node_cnt; ++i) {
dep[i] = -1; gap[i] = 0;
}
dep[t] = 0; gap[0] = 1;
queue<LL> q; q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
for (LL i = front[u]; i; i = e[i].next) {
LL v = e[i].v;
if (dep[v] != -1) continue;
q.push(v); dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
return;
}
LL dfs(LL u, LL flow) {
if (u == t) {
maxflow += flow; return flow;
}
LL used = 0;
for (LL i = front[u]; i; i = e[i].next) {
LL d = e[i].v;
if (e[i].w and dep[d] + 1 == dep[u]) {
LL mi = dfs(d, min(e[i].w, flow - used));
if (mi) {
e[i].w -= mi; e[i ^ 1ll].w += mi;
used += mi;
}
if (used == flow) return used;
}
}
--gap[dep[u]];
if (gap[dep[u]] == 0) dep[s] = node_cnt + 1;
++dep[u]; ++gap[dep[u]];
return used;
}
void opt() {
maxflow = 0;
bfs();
while (dep[s] < node_cnt) dfs(s, INF);
}
}isap;
void dfs(int u) {
vis[u] = 1;
for (int i = front[u]; i; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if (vis[v]) continue;
if (w > 0) dfs(v);
}
}
void main2() {
isap.node_cnt = n + 2;
s = n + 1;
t = n + 2;
for (int i = 1; i <= n + 2; ++i) {
front[i] = 0;
}
en = 1;
addEdge(s, 1, INF);
addEdge(2, t, INF);
for (int i = 1; i <= m; ++i) {
int x, y, z;
cin >> x >> y >> z;
addEdge(x, y, z);
addEdge(y, x, 0);
addEdge(y, x, z);
addEdge(x, y, 0);
}
isap.opt();
for (int i = 1; i <= n + 2; ++i) {
vis[i] = 0;
}
vector<pair<int, int> > ans;
dfs(1);
for (int i = 2; i <= en; i += 2) {
int u = e[i].u, v = e[i].v;
if (u > v or u > n or v > n) continue;
if (vis[u] ^ vis[v]) {
cout << u << ' ' << v << '\n';
}
}
cout << '\n';
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (1) {
cin >> n >> m;
if (!n and !m) break;
main2();
}
return 0;
}