一些经典题目
找可达路径
最小割建模
/**
* 洛谷 P3227 [HNOI2013] 切糕
*/
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define INF 1010
#define N 74088
struct edge {int u, v, cap, flow;}; vector<edge> e; vector<int> g[N];
int q[N], p[N], d[N], cur[N], num[N], P, Q, R, D; bool vis[N];
void add_edge(int u, int v, int cap) {
g[u].push_back(e.size()); e.push_back({u, v, cap, 0});
g[v].push_back(e.size()); e.push_back({v, u, 0, 0});
}
bool bfs(int s, int t) {
memset(vis, 0, sizeof(vis)); memset(d, 0, sizeof(d)); q[0] = t; d[t] = 0; vis[t] = true;
int head = 0, tail = 1;
while (head < tail) {
int v = q[head++];
for (int i=0; i<g[v].size(); ++i) {
const edge& ee = e[g[v][i]^1];
if (!vis[ee.u] && ee.cap > ee.flow) vis[ee.u] = true, d[ee.u] = d[v] + 1, q[tail++] = ee.u;
}
}
return vis[s];
}
int solve() {
int s = 0, t = P*Q*R + 1, cc = 0; e.clear();
for (int i=0; i<N; ++i) g[i].clear();
for (int z=0; z<R; ++z) for (int x=1; x<=P; ++x) for (int y=1; y<=Q; ++y) {
int u = P*Q*z + (x-1)*Q + y, v; cin >> v;
add_edge(z ? u-P*Q : s, u, v);
if (z < D) continue;
if (x > 1) add_edge(u, u - P*Q*D - Q, INF);
if (x < P) add_edge(u, u - P*Q*D + Q, INF);
if (y > 1) add_edge(u, u - P*Q*D - 1, INF);
if (y < Q) add_edge(u, u - P*Q*D + 1, INF);
}
for (int x=1; x<=P; ++x) for (int y=1; y<=Q; ++y) add_edge(P*Q*(R-1) + (x-1)*Q + y, t, INF);
if (!bfs(s, t)) return 0;
memset(num, 0, sizeof(num)); memset(cur, 0, sizeof(cur));
for (int i=0; i<=t; ++i) ++num[d[i]];
int u = s;
while (d[s] <= t) {
if (u == t) {
int a = INF;
for (int v=t; v!=s; v = e[p[v]].u) a = min(a, e[p[v]].cap - e[p[v]].flow);
for (int v=t; v!=s; v = e[p[v]].u) e[p[v]].flow += a, e[p[v]^1].flow -= a;
cc += a; u = s;
}
int ok = 0;
for (int i=cur[u]; i<g[u].size(); ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[u] == d[ee.v] + 1) {
ok = 1; p[ee.v] = g[u][i]; cur[u] = i; u = ee.v;
break;
}
}
if (!ok) {
int m = t;
for (int i=0; i<g[u].size(); ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow) m = min(m, d[ee.v]);
}
if (--num[d[u]] == 0) break;
++num[d[u] = m + 1]; cur[u] = 0;
if (u != s) u = e[p[u]].u;
}
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> P >> Q >> R >> D) cout << solve() << endl;
return 0;
}
UVa1660/LA3031 Cable TV Network
费用流建模
P1251 餐巾计划问题
本题建图的拆点方式和多数网络流的拆点方式不同,以往拆点后点对
i
,
i
′
i,i^′
i,i′之间会连边从而满足流量限制。而此题拆点后
i
,
i
′
i,i^′
i,i′之间是割裂的,此题是将点
i
i
i当做当天用后的脏毛巾,点
i
′
i^′
i′则当作当天需要的干净毛巾。当天干净毛巾需求量已知并且获取途径已知(之前某天的脏毛巾快洗/慢洗,或者直接购买新毛巾),当天产生的脏毛巾总量已知并且转移途径已知(积压到下一天、快洗/慢洗变成之后某天的干净毛巾)。根据这些约束建图求最小费用即可。
建图方式:源点
S
S
S向每个结点
i
i
i连边,容量为
r
i
r_i
ri,费用为0;每个结点
i
′
i^′
i′向汇点
T
T
T连边,容量为
r
i
r_i
ri,费用为0;源点
S
S
S向每个结点
i
′
i^′
i′连边,容量为
i
n
f
inf
inf(也可以是
r
i
r_i
ri),费用为
p
p
p;每个结点
i
i
i向
(
i
+
m
)
′
(i+m)^′
(i+m)′连边,容量为
i
n
f
inf
inf(也可以是
r
i
+
m
r_{i+m}
ri+m),费用为
f
f
f;每个结点
i
i
i向
(
i
+
n
)
′
(i+n)^′
(i+n)′连边,容量为
i
n
f
inf
inf(也可以是
r
i
+
n
r_{i+n}
ri+n),费用为
s
s
s;每个结点
i
i
i向
i
+
1
i+1
i+1连边,容量为
i
n
f
inf
inf,费用为0。
#include <iostream>
#include <cstring>
using namespace std;
#define INF 30000000000
#define N 4004
struct edge {int u, v, cost; long long cap, flow;} e[6*N];
int g[N][N], q[6*N*N], d[N], p[N], cnt[N], r[N], c, n; long long a[N]; bool vis[N];
void add_edge(int u, int v, long long cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
long long solve() {
memset(cnt, 0, sizeof(cnt));
long long cc = 0; int s = 0, t = 2*n+1;
for (int i=1; i<=n; ++i) cin >> r[i];
int _p, _m, _f, _n, _s; cin >> _p >> _m >> _f >> _n >> _s;
for (int i=1; i<=n; ++i) {
add_edge(s, i, r[i], 0); add_edge(i+n, t, r[i], 0); add_edge(0, i+n, INF, _p);
if (i < n) add_edge(i, i+1, INF, 0);
if (_m+i <= n) add_edge(i, _m+i+n, INF, _f);
if (_n+i <= n) add_edge(i, _n+i+n, INF, _s);
}
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
d[s] = 0; q[0] = s; a[s] = INF;
int head = 0, tail = 1;
while (head < tail) {
int u = q[head++]; vis[u] = false;
for (int i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= 20000) return cc;
cc += d[t] * a[t];
for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n) cout << solve() << endl;
return 0;
}
#include <iostream>
#include <cstring>
using namespace std;
#define INF 20000000
#define N 802
struct edge {int u, v, cap, flow, cost;} e[12*N]; char s1[22][22], s2[22][22], s3[22][22];
int g[N][N>>1], q[12*N*N], a[N], d[N], p[N], cnt[N], c, m, n; bool vis[N];
void add_edge(int u, int v, int cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
int solve() {
for (int i=1; i<=n; ++i) cin >> s1[i]+1;
for (int i=1; i<=n; ++i) cin >> s2[i]+1;
for (int i=1; i<=n; ++i) cin >> s3[i]+1;
memset(cnt, c = 0, sizeof(cnt));
int s = 0, t = 2*m*n+1, f = 0, cc = 0;
for (int i=1, x=1; i<=n; ++i) for (int j=1; j<=m; ++j, ++x) {
int k = s3[i][j] - '0', y = x+m*n;
if (s1[i][j] == '1') {
if (s2[i][j] == '0') add_edge(s, x, 1, 0), ++f;
} else if (s2[i][j] == '1') add_edge(y, t, 1, 0), --f;
add_edge(x, y, k&1 && s1[i][j] != s2[i][j] ? (k+1)>>1 : k>>1, 0);
if (i > 1) {
add_edge(y, x-m, INF, 1);
if (j > 1) add_edge(y, x-m-1, INF, 1);
if (j < m) add_edge(y, x-m+1, INF, 1);
}
if (j > 1) add_edge(y, x-1, INF, 1);
if (j < m) add_edge(y, x+1, INF, 1);
if (i < n) {
add_edge(y, x+m, INF, 1);
if (j > 1) add_edge(y, x+m-1, INF, 1);
if (j < m) add_edge(y, x+m+1, INF, 1);
}
}
if (f) return -1;
while (true) {
memset(d, 0x7f, sizeof(d)); memset(vis, 0, sizeof(vis));
d[s] = 0; q[0] = s; a[s] = INF;
int head = 0, tail = 1;
while (head < tail) {
short u = q[head++]; vis[u] = false;
for (short i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= INF) return cc;
cc += d[t] * a[t];
for (short u=t; u!=s; u=e[p[u]].u) {
e[p[u]].flow += a[t];
e[p[u]^1].flow -= a[t];
}
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> m) cout << solve() << endl;
return 0;
}
洛谷 P4003 [2017国家集训队测试] 无限之环
此题仅分析有无解的话,就是最大流问题,建图方式也很自然能想到。要求最优解则变成了费用流问题了,这个时候费用安排在哪就是个难点了,按水管的不同形状依次做分析还是能找到突破口。
可以把水管分成上图四类:第一行不能转(题目特地交待的只能转非直线型水管)或者不需要转,不需要安排费用;第二行只有一个口,根据旋转情况直接知道费用该怎么安排;第三行,转90度的话可看成只有一个口位置变了,转180度的话两个口转到对面了,费用也知道该怎么安排;第四行和第二行类似。
题目暗搓搓的交代只能转非直线型水管,就是为了使得费用能安排下去,如果直线型水管也能转就不行了。
#include <iostream>
#include <cstring>
using namespace std;
#define T u+d
#define R u+2*d
#define B u+3*d
#define L u+4*d
#define N 10002
struct edge {int u, v, cap, flow, cost;} e[N<<2];
int g[N][2002], q[N*N<<2], a[N], d[N], p[N], cnt[N], c, m, n; bool vis[N];
void add_edge(int u, int v, int cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
int solve() {
int s = 0, t = 5*m*n + 1, b = 0, f = 0, cc = 0; memset(cnt, c = 0, sizeof(cnt));
for (int i=1, u=1, d=m*n; i<=n; ++i) for (int j=1; j<=m; ++j, ++u) {
int x; cin >> x;
if (!x) continue;
if ((i+j) & 1) {
add_edge(s, u, 4, 0);
if (x == 1) ++f, add_edge(u, T, 1, 0), add_edge(T, R, 1, 1), add_edge(T, L, 1, 1), add_edge(T, B, 1, 2);
if (x == 2) ++f, add_edge(u, R, 1, 0), add_edge(R, T, 1, 1), add_edge(R, B, 1, 1), add_edge(R, L, 1, 2);
if (x == 4) ++f, add_edge(u, B, 1, 0), add_edge(B, L, 1, 1), add_edge(B, R, 1, 1), add_edge(B, T, 1, 2);
if (x == 8) ++f, add_edge(u, L, 1, 0), add_edge(L, T, 1, 1), add_edge(L, B, 1, 1), add_edge(L, R, 1, 2);
if (x == 3) f+=2, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(T, B, 1, 1), add_edge(R, L, 1, 1);
if (x == 6) f+=2, add_edge(u, B, 1, 0), add_edge(u, R, 1, 0), add_edge(B, T, 1, 1), add_edge(R, L, 1, 1);
if (x == 9) f+=2, add_edge(u, T, 1, 0), add_edge(u, L, 1, 0), add_edge(T, B, 1, 1), add_edge(L, R, 1, 1);
if (x == 12) f+=2, add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(B, T, 1, 1), add_edge(L, R, 1, 1);
if (x == 7) f+=3, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(T, L, 1, 1), add_edge(B, L, 1, 1), add_edge(R, L, 1, 2);
if (x == 11) f+=3, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, L, 1, 0), add_edge(R, B, 1, 1), add_edge(L, B, 1, 1), add_edge(T, B, 1, 2);
if (x == 13) f+=3, add_edge(u, T, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(T, R, 1, 1), add_edge(B, R, 1, 1), add_edge(L, R, 1, 2);
if (x == 14) f+=3, add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(R, T, 1, 1), add_edge(L, T, 1, 1), add_edge(B, T, 1, 2);
if (x == 5) f+=2, add_edge(u, T, 1, 0), add_edge(u, B, 1, 0);
if (x == 10) f+=2, add_edge(u, R, 1, 0), add_edge(u, L, 1, 0);
if (x == 15) f+=4, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0);
if (i > 1) add_edge(T, B-m, 1, 0);
if (j > 1) add_edge(L, R-1, 1, 0);
if (i < n) add_edge(B, T+m, 1, 0);
if (j < m) add_edge(R, L+1, 1, 0);
} else {
add_edge(u, t, 4, 0);
if (x == 1) ++b, add_edge(T, u, 1, 0), add_edge(R, T, 1, 1), add_edge(L, T, 1, 1), add_edge(B, T, 1, 2);
if (x == 2) ++b, add_edge(R, u, 1, 0), add_edge(T, R, 1, 1), add_edge(B, R, 1, 1), add_edge(L, R, 1, 2);
if (x == 4) ++b, add_edge(B, u, 1, 0), add_edge(L, B, 1, 1), add_edge(R, B, 1, 1), add_edge(T, B, 1, 2);
if (x == 8) ++b, add_edge(L, u, 1, 0), add_edge(T, L, 1, 1), add_edge(B, L, 1, 1), add_edge(R, L, 1, 2);
if (x == 3) b+=2, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, T, 1, 1), add_edge(L, R, 1, 1);
if (x == 6) b+=2, add_edge(B, u, 1, 0), add_edge(R, u, 1, 0), add_edge(T, B, 1, 1), add_edge(L, R, 1, 1);
if (x == 9) b+=2, add_edge(T, u, 1, 0), add_edge(L, u, 1, 0), add_edge(B, T, 1, 1), add_edge(R, L, 1, 1);
if (x == 12) b+=2, add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(T, B, 1, 1), add_edge(R, L, 1, 1);
if (x == 7) b+=3, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, T, 1, 1), add_edge(L, B, 1, 1), add_edge(L, R, 1, 2);
if (x == 11) b+=3, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(L, u, 1, 0), add_edge(B, R, 1, 1), add_edge(B, L, 1, 1), add_edge(B, T, 1, 2);
if (x == 13) b+=3, add_edge(T, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(R, T, 1, 1), add_edge(R, B, 1, 1), add_edge(R, L, 1, 2);
if (x == 14) b+=3, add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(T, R, 1, 1), add_edge(T, L, 1, 1), add_edge(T, B, 1, 2);
if (x == 5) b+=2, add_edge(T, u, 1, 0), add_edge(B, u, 1, 0);
if (x == 10) b+=2, add_edge(R, u, 1, 0), add_edge(L, u, 1, 0);
if (x == 15) b+=4, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0);
}
}
if (b != f) return -1;
f = 0;
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
d[s] = 0; q[0] = s; a[s] = N;
int head = 0, tail = 1;
while (head < tail) {
short u = q[head++]; vis[u] = false;
for (short i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= N) break;
f += a[t]; cc += d[t] * a[t];
for (short u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
}
return f == b ? cc : -1;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> m) cout << solve() << endl;
return 0;
}
一些可以转化为二分图最大权匹配的建模题
UVa1006/LA2238 Fixed Partition Memory Management
跑费用流的过程中动态加点连边
洛谷P2050 [NOI2012] 美食节
这道题目是洛谷P2053 [SCOI2007] 修车的加强版,数据量变大
(
n
≤
40
,
m
≤
100
,
P
≤
800
,
t
i
,
j
≤
1000
,其中
P
=
∑
p
i
)
(n≤40,m≤100,P≤800,t_{i,j}≤1000,其中P=∑p_i)
(n≤40,m≤100,P≤800,ti,j≤1000,其中P=∑pi),如果继续用KM算法求二分图最大权匹配的话,点数的规模是 𝑂(𝑚𝑃 + 𝑃),边数的规模是 𝑂(𝑃𝑚𝑃),会超时。
如果直接用费用流求解,则点数的规模是𝑂(𝑚𝑃 + 𝑛),边数的规模是 𝑂(𝑛𝑚𝑃),也会超时。要想办法去掉冗余的点和边(每个厨师不会都要做𝑃道菜)。由于费用流算法每次只能找出一条增广路,所以可以先不连不需要的边。一开始,把所有厨师做倒数第1道菜与所有菜连好,然后找一条增广路,表示第j个厨师做倒数第1道菜,然后继续添加点(第j个厨师做倒数第2道菜),与汇点和所有菜连边,以此类推。这样做点数的规模是𝑂(𝑚+𝑛+𝑃),边数的规模是 𝑂(𝑛𝑃)。
#include <iostream>
#include <cstring>
using namespace std;
#define INF 900000
#define N 42
#define M 102
#define P 1002
struct edge {int u, v, cap, flow, cost, c;} e[N*P<<1];
int g[P][P], x[N][M], y[M], q[N*P*P<<1], a[P], d[P], p[P], cnt[P], c, m, n; bool vis[P];
void add_edge(int u, int v, int cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
int solve() {
memset(cnt, c = 0, sizeof(cnt));
int s = 0, t = n+1, b = t, f, cc = 0;
for (int i=1, c; i<=n; ++i) cin >> c, add_edge(0, i, c, 0);
for (int i=1; i<=n; ++i) for (int j=1; j<=m; ++j)
cin >> x[i][j], add_edge(i, b+j, 1, x[i][j]);
for (int i=1; i<=m; ++i) y[i] = 1, e[c].c = i, add_edge(++b, t, 1, 0);
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
d[s] = 0; q[0] = s; a[s] = INF;
int head = 0, tail = 1;
while (head < tail) {
short u = q[head++]; vis[u] = false;
for (short i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= INF) return cc;
cc += d[t];
for (short u=t; u!=s; u=e[p[u]].u) {
e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
if (e[p[u]].v == t && e[p[u]].flow) f = e[p[u]].c;
}
++y[f]; e[c].c = f; add_edge(++b, t, 1, 0);
for (int i=1; i<=n; ++i) add_edge(i, b, 1, y[f]*x[i][f]);
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> m) cout << solve() << endl;
return 0;
}
知识点
平面图最小割=对偶图最短路
上下界网络流
上下界网络流,包括4种类型:无源无汇有容量上下界网络的可行流、有源有汇有容量上下界网络的可行流、有源有汇有容量上下界网络的最大流、有源有汇有容量上下界网络的最小流。
二分图
区间k覆盖问题
将区间两端点加入数组,排序后去重离散化,设不同坐标数量为
m
m
m,以0号(最小坐标)作为源点,新增汇点
t
=
m
t=m
t=m。建图跑费用流:
i
→
i
+
1
i\rightarrow i+1
i→i+1,流量为
k
k
k费用为0;对区间
[
i
,
j
)
[i,j)
[i,j)连边
i
→
j
i\rightarrow j
i→j,流量为1费用为负边权
−
w
-w
−w。
注意上面的建图方法是针对开区间(全开或者半开半闭),如果数据存在闭区间,则需要将每个点一拆为二。
P3358 最长k可重区间集问题
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 1002
struct edge {int u, v, cap, flow, cost;} e[3*N];
int g[N][N>>1], q[3*N*N], a[N], d[N], p[N], cnt[N], l[N], r[N], x[N], c, m, n, k; bool vis[N];
void add_edge(int u, int v, int cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
int solve() {
for (int i=m=0; i<n; ++i) cin >> l[i] >> r[i], x[m++] = l[i], x[m++] = r[i];
sort(x, x+m); m = unique(x, x+m) - x; memset(cnt, c = 0, sizeof(cnt));
int s = 0, t = m, cc = 0;
for (int i=0; i<m; ++i) add_edge(i, i+1, k, 0);
for (int i=0; i<n; ++i) add_edge(lower_bound(x, x+m, l[i]) - x, lower_bound(x, x+m, r[i]) - x, 1, l[i] - r[i]);
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis)); d[s] = 0; q[0] = s; a[s] = k;
int head = 0, tail = 1;
while (head < tail) {
int u = q[head++]; vis[u] = false;
for (int i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= 0) break;
cc -= d[t] * a[t];
for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> k) cout << solve() << endl;
return 0;
}
P3357 最长k可重线段集问题,这个题就是存在闭区间(与 x x x轴垂直的线段),需要拆点。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
#define N 2002
struct edge {int u, v, cap, flow; long long cost;} e[3*N]; long long d[N];
int g[N][N>>1], q[3*N*N], a[N], p[N], cnt[N], xl[N], yl[N], xr[N], yr[N], x[N], c, m, n, k; bool vis[N];
void add_edge(int u, int v, int cap, long long cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
int solve() {
for (int i=m=0; i<n; ++i) cin >> xl[i] >> yl[i] >> xr[i] >> yr[i], x[m++] = xl[i], x[m++] = xr[i];
sort(x, x+m); m = unique(x, x+m) - x; memset(cnt, c = 0, sizeof(cnt));
int s = 0, t = 2*m; long long cc = 0;
for (int i=0; i<m; ++i) add_edge(i, i+m, k, 0), add_edge(i+m, i+1==m ? t : i+1, k, 0);
for (int i=0; i<n; ++i) {
double dx = xl[i]-double(xr[i]), dy = yl[i]-double(yr[i]);
int l = lower_bound(x, x+m, xl[i]) - x, r = lower_bound(x, x+m, xr[i]) - x;
if (l == r) {
add_edge(l, m+l, 1, -sqrt(dx*dx + dy*dy));
continue;
}
if (l > r) l = l+r, r = l-r, l = l-r;
add_edge(m+l, r, 1, -sqrt(dx*dx + dy*dy));
}
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis)); d[s] = 0; q[0] = s; a[s] = k;
int head = 0, tail = 1;
while (head < tail) {
int u = q[head++]; vis[u] = false;
for (int i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= 0) break;
cc -= d[t] * a[t];
for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
}
return cc;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> k) cout << solve() << endl;
return 0;
}
混合图的欧拉回路
UVa10735 Euler Circuit
本题有洛谷 P3159 [CQOI2012] 交换棋子的影子,不过比洛谷 P3159 [CQOI2012] 交换棋子要简单一点。
诡异的是,我写ISAP跑最大流就会WA,换成朴素的Edmonds–Karp算法跑最大流就就AC。
#include <iostream>
#include <cstring>
using namespace std;
#define M 600
#define N 102
struct edge {int u, v, cap, flow;} e[M<<1];
int g[N][M], q[M*N<<1], p[M], a[N], d[N], cnt[N], u[M], v[M], h[M], c, m, n; bool vis[M];
void add_edge(int u, int v, int cap) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; g[v][cnt[v]++] = c++;
}
void euler(int x) {
for (int i=0, j; i<cnt[x]; ++i) if (!vis[j = g[x][i]]) vis[j] = true, euler(u[j]+v[j]-x), p[n++] = x;
}
bool solve() {
cin >> n >> m; memset(cnt, c = 0, sizeof(cnt)); memset(d, 0, sizeof(d));
for (int i=0; i<m; ++i) {
char x; cin >> u[i] >> v[i] >> x; ++d[u[i]]; --d[v[i]]; h[i] = -1;
if (x == 'U') h[i] = c, add_edge(u[i], v[i], 1);
}
int s = 0, t = n+1, f = 0;
for (int i=1; i<=n; ++i) {
if (d[i] & 1) return false;
if (d[i] > 0) add_edge(s, i, d[i]>>1), f += d[i]>>1;
if (d[i] < 0) add_edge(i, t, -d[i]>>1);
}
if (f) {
while (true) {
memset(a, 0, sizeof(a)); a[s] = m; q[0] = s;
int head = 0, tail = 1;
while (head < tail) {
int u = q[head++];
for (int i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (!a[ee.v] && ee.cap > ee.flow) {
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
q[tail++] = ee.v;
}
}
if (a[t]) break;
}
if (!a[t]) break;
f -= a[t];
for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
}
}
if (f) return false;
memset(cnt, n = 0, sizeof(cnt));
for (int i=0; i<m; ++i) {
vis[i] = false; h[i] >= 0 && e[h[i]].flow ? g[v[i]][cnt[v[i]]++] = i : g[u[i]][cnt[u[i]]++] = i;
}
euler(1);
while (n--) cout << p[n] << ' ';
cout << 1 << endl;
return true;
}
int main() {
int t; cin >> t;
while (t--) {
if (!solve()) cout << "No euler circuit exist" << endl;
if (t) cout << endl;
}
return 0;
}
最大权闭合子图
洛谷P2762 太空飞行计划问题
一些概念:对于一个有向图G,存在点集合V,任取点u属于V,u的所有出边的另一点也属于V,则为闭合子图。当每个点有一个权值w(有正有负),点权和最大的闭合子图为最大权闭合子图。
建图求出最大流
f
l
o
w
flow
flow后,记正权和为
s
u
m
sum
sum,则最大权和为
s
u
m
−
f
l
o
w
sum-flow
sum−flow。
说一下求最大权闭合子图点集V的方法:根据所采用的最大流算法求出S点集和T点集,S点集去掉源点就是最大权闭合子图点集V。
#include <iostream>
#include <cstring>
using namespace std;
#define INF 0x7f7f7f7f
#define N 102
struct edge {int u, v, cap, flow;} e[5200];
int g[N][N>>1], q[N], p[N], d[N], cur[N], num[N], cnt[N], c, m, n; bool vis[N];
void add_edge(int u, int v, int cap) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; g[v][cnt[v]++] = c++;
}
bool bfs(int s, int t) {
memset(vis, 0, sizeof(vis)); memset(d, 0, sizeof(d)); q[0] = t; d[t] = 0; vis[t] = true;
int head = 0, tail = 1;
while (head < tail) {
int v = q[head++];
for (int i=0; i<cnt[v]; ++i) {
const edge& ee = e[g[v][i]^1];
if (!vis[ee.u] && ee.cap > ee.flow) vis[ee.u] = true, d[ee.u] = d[v] + 1, q[tail++] = ee.u;
}
}
return vis[s];
}
void solve() {
int s = 0, t = m+n+1, cc = 0, u = s; memset(cnt, c = 0, sizeof(cnt));
for (int i=1; i<=m; ++i) {
int x; cin >> x; cc += x; add_edge(s, i, x);
while ((x = cin.peek()) != '\r' && x != '\n') cin >> x, add_edge(i, x+m, INF);
}
for (int i=1, x; i<=n; ++i) cin >> x, add_edge(i+m, t, x);
bfs(s, t); memset(num, 0, sizeof(num)); memset(cur, 0, sizeof(cur));
for (int i=0; i<=t; ++i) ++num[d[i]];
while (d[s] <= t) {
if (u == t) {
int a = INF;
for (int v=t; v!=s; v = e[p[v]].u) a = min(a, e[p[v]].cap - e[p[v]].flow);
for (int v=t; v!=s; v = e[p[v]].u) e[p[v]].flow += a, e[p[v]^1].flow -= a;
cc -= a; u = s;
}
int ok = 0;
for (int i=cur[u]; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[u] == d[ee.v] + 1) {
ok = 1; p[ee.v] = g[u][i]; cur[u] = i; u = ee.v;
break;
}
}
if (!ok) {
int m = t;
for (int i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow) m = min(m, d[ee.v]);
}
if (--num[d[u]] == 0) break;
++num[d[u] = m + 1]; cur[u] = 0;
if (u != s) u = e[p[u]].u;
}
}
bfs(s, t);
for (int i=0, k=0; i<cnt[s]; ++i) {
const edge &ee = e[g[s][i]];
if (!vis[ee.v]) {
if (k++) cout << ' ';
cout << ee.v;
}
}
cout << endl;
for (int i=0, k=0; i<cnt[t]; ++i) {
const edge &ee = e[g[t][i]^1];
if (!vis[ee.u]) {
if (k++) cout << ' ';
cout << ee.u-m;
}
}
cout << endl << cc << endl;
}
int main() {
while (cin >> m >> n) solve();
return 0;
}
最大密度子图
[待完善