EK就跳过了,计划noip前尽量做完网络流24题吧。
//上面这个flag已弃。。。现在主要任务是水dp题
//偶尔会肝一道防止忘记板子
【模板】网络最大流 dinic
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 1000010
#define INF 999999999
using namespace std;
struct fls {
int to, next, val;
}edge[MAXN << 1];
int head[MAXN << 1], level[MAXN], q[MAXN], n, m, S, T, cnt, t, w;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w) {
edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}
inline bool BFS () {
memset (level, -1, sizeof level);
memset (q, 0, sizeof q);
level[S] = 0, q[t = w = 1] = S;
while (t <= w) {
int now = q[t];
for (register int i = head[now]; i != -1; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].val != 0 && level[v] == -1) {
level[v] = level[now] + 1;
q[++w] = v;
}
}
t++;
}
if (level[T] != -1) return 1;
return 0;
}
int DFS (int now, int flow) {
int ret = flow; if (now == T) return flow;
for (register int i = head[now]; i != -1; i = edge[i].next) {
int v = edge[i].to; if (ret <= 0) break;
if (edge[i].val != 0 && level[now] + 1 == level[v]) {
int o = DFS (v, min (edge[i].val, ret));
ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
}
}
return flow - ret;
}
void dinic () {
int ans = 0;
while (BFS () == 1) ans += DFS (S, INF);
printf ("%d\n", ans);
}
int main () {
memset (head, -1, sizeof head), cnt = 1;
n = read (), m = read (), S = read (), T = read ();
for (register int i = 1; i <= m; i++) {
int u = read (), v = read (), w = read ();
connect (u, v, w), connect (v, u, 0);
}
dinic (); return 0;
}
【模板】二分图匹配 dinic
二分图dinic
O
(
n
2
n
)
O(n^2\sqrt n)
O(n2n) 吊打暴力匈牙利
O
(
n
3
)
O(n^3)
O(n3)
思路:建立超级源点
S
(
0
)
S(0)
S(0),超级汇点
T
(
n
1
+
n
2
+
1
)
T(n1+n2+1)
T(n1+n2+1)
S
S
S向二分图左部连边,二分图右部向
T
T
T连边,二分图内部按要求互相连边。
所有正向边边权为1,反向边边权为0,跑一边dinic即可。
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 1000010
#define INF 999999999
using namespace std;
struct fls {
int to, next, val;
}edge[MAXN << 1];
int head[MAXN << 1], level[MAXN], q[MAXN], n, m, S, T, n1, n2, t, w, cnt = 1;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w) {
edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}
inline bool BFS () {
memset (level, -1, sizeof level);
memset (q, 0, sizeof q);
level[S] = 0, q[t = w = 1] = S;
while (t <= w) {
int now = q[t];
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].val != 0 && level[v] == -1) {
level[v] = level[now] + 1;
q[++w] = v;
}
}
t++;
}
if (level[T] != -1) return 1;
return 0;
}
int DFS (int now, int flow) {
int ret = flow; if (now == T) return flow;
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to; if (ret <= 0) break;
if (edge[i].val != 0 && level[now] + 1 == level[v]) {
int o = DFS (v, min (edge[i].val, ret));
ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
}
}
return flow - ret;
}
void dinic () {
int ans = 0;
while (BFS () == 1) ans += DFS (S, INF);
printf ("%d\n", ans);
}
int main () {
n1 = read (), n2 = read (), m = read (), S = 0, T = n1 + n2 + 1;
for (register int i = 1; i <= n1; i++) connect (S, i, 1), connect (i, S, 0);
for (register int i = n1 + 1; i <= n1 + n2; i++) connect (i, T, 1), connect (T, i, 0);
for (register int i = 1; i <= m; i++) {
int u = read (), v = read () + n1;
if (u > n1 || v > n1 + n2) continue;
connect (u, v, 1), connect (v, u, 0);
}
dinic (); return 0;
}
飞行员配对方案问题 dinic
一开始也是建图二分图上跑dinic,和模板唯一不同的就是需要输出两两匹配的方案。
思路:先排除与超级源点和超级汇点相连的边,因为我们要的是二分图内部的边。
输出方案的话,看看这条边的反向边有没有流量,有流量就是启用了,输出这条边的两端点即可。
不用纠结顺序,有spj
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define INF 999999999
#define MAXN 100010
using namespace std;
struct fls {
int to, next, val;
}edge[MAXN << 1];
int head[MAXN << 1], q[MAXN], level[MAXN], n1, n2, S, T, t, w, cnt = 1, ans;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w) {
edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}
inline bool BFS () {
memset (level, -1, sizeof level);
level[S] = 0, q[t = w = 1] = S;
while (t <= w) {
int now = q[t];
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].val != 0 && level[v] == -1) {
level[v] = level[now] + 1;
q[++w] = v;
}
}
t++;
}
if (level[T] == -1) return 0;
return 1;
}
int DFS (int now, int flow) {
int ret = flow; if (now == T) return flow;
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to; if (ret <= 0) break;
if (edge[i].val != 0 && level[now] + 1 == level[v]) {
int o = DFS (v, min (ret, edge[i].val));
ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
}
}
return flow - ret;
}
void dinic () {
while (BFS ()) ans += DFS (S, INF);
}
int main () {
n1 = read (), n2 = read (), S = 0, T = n1 + n2 + 1;
for (;;) {
int u = read (), v = read ();
if (u == -1 || v == -1) break;
connect (u, v, 1), connect (v, u, 0);
}
for (register int i = 1; i <= n1; i++) connect (S, i, 1), connect (i, S, 0);
for (register int i = n1 + 1; i <= n1 + n2; i++) connect (i, T, 1), connect (T, i, 0);
dinic (); if (ans == 0) return puts ("No Solution"), 0;
printf ("%d\n", ans);
for (register int i = 2; i <= cnt; i += 2) {
if (edge[i].to == S || edge[i ^ 1].to == S) continue;
if (edge[i].to == T || edge[i ^ 1].to == T) continue;
if (edge[i ^ 1].val != 0) printf ("%d %d\n", edge[i].to, edge[i ^ 1].to);
}
return 0;
}
【模板】最小费用最大流 费用流
按照题意连边,反向边流量为0,费用为原边相反数。
一直做
S
P
F
A
SPFA
SPFA直到汇点
T
T
T没有新增流量(找不到新的增广路)了为止,统计入答案
maxflow & mincost
\text {maxflow~\& mincost}
maxflow & mincost
核心内容就是在
S
P
F
A
SPFA
SPFA松弛操作时保存信息,具体操作是用pre[]
数组记录路径(从哪个点来),last[]
记录边。
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 100010
using namespace std;
struct fls {
int to, next, dist, flow;
}edge[MAXN << 1];
int head[MAXN << 1], vis[MAXN], dist[MAXN], flow[MAXN], pre[MAXN], q[MAXN], last[MAXN];
int cnt = 1, n, m, S, T, maxflow, mincost, t, w;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w, int o) {
edge[++cnt].to = v, edge[cnt].flow = w, edge[cnt].dist = o,
edge[cnt].next = head[u], head[u] = cnt;
}
inline bool SPFA () {
memset (dist, 0x3f3f3f3f, sizeof dist);
memset (flow, 0x3f3f3f3f, sizeof flow);
memset (vis, 0, sizeof vis);
q[t = w = 1] = S, vis[S] = 1, dist[S] = 0, pre[T] = -1;
while (t <= w) {
int now = q[t]; t++; vis[now] = 0;
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].flow > 0 && dist[v] > dist[now] + edge[i].dist) {
dist[v] = dist[now] + edge[i].dist;
pre[v] = now; last[v] = i;
flow[v] = min (flow[now], edge[i].flow);
if (! vis[v]) {
vis[v] = 1; q[++w] = v;
}
}
}
}
return pre[T] != -1;
}
void MCMF () {
while (SPFA ()) {
int now = T;
maxflow += flow[now], mincost += flow[now] * dist[now];
while (now != S) {
edge[last[now]].flow -= flow[T];
edge[last[now] ^ 1].flow += flow[T];
now = pre[now];
}
}
printf ("%d %d\n", maxflow, mincost);
}
int main () {
n = read (), m = read (), S = read (), T = read ();
for (register int i = 1; i <= m; i++) {
int u = read (), v = read (), w = read (), o = read ();
connect (u, v, w, o), connect (v, u, 0, -o);
}
MCMF ();
return 0;
}
负载平衡问题 费用流
本题与[HAOI2008]糖果传递和均分纸牌模板一样
可以解绝对值方程组或贪心
O
(
n
)
O(n)
O(n)解决,考虑费用流,令
a
v
e
=
∑
i
=
1
n
a
i
n
ave=\frac{\sum_{i=1}^n a_i}{n}
ave=n∑i=1nai,即调整后每个节点的值。
建立源点与汇点,小于
a
v
e
ave
ave的需要流,于是连向汇点,大于
a
v
e
ave
ave的需要分流,于是从源点连向它。边的流量为与
a
v
e
ave
ave的差值,费用为0(因为是虚拟的)
环上的点与它左右两边的点均连边,流量无限,费用为1(可以随便你怎么流,而且流是要花费代价的)
注意环的处理。建完图套
M
C
M
F
MCMF
MCMF即可
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define INF 999999999
#define MAXN 100000
using namespace std;
struct fls {
int to, next, flow, dist;
}edge[MAXN << 1];
bool vis[MAXN];
int head[MAXN << 1], dist[MAXN], flow[MAXN], pre[MAXN], q[MAXN], id[MAXN];
int a[MAXN], mincost, S, T, n, t, w, cnt = 1;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w, int o) {
edge[++cnt].to = v, edge[cnt].flow = w, edge[cnt].dist = o,
edge[cnt].next = head[u], head[u] = cnt;
}
inline bool SPFA () {
memset (dist, 0x3f3f3f3f, sizeof dist);
memset (flow, 0x3f3f3f3f, sizeof flow);
memset (vis, 0, sizeof vis);
q[t = w = 1] = S, vis[S] = 1, dist[S] = 0, pre[T] = -1;
while (t <= w) {
int now = q[t]; t++; vis[now] = 0;
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].flow > 0 && dist[v] > dist[now] + edge[i].dist) {
dist[v] = dist[now] + edge[i].dist,
flow[v] = min (flow[now], edge[i].flow),
pre[v] = now, id[v] = i;
if (! vis[v]) {
vis[v] = 1, q[++w] = v;
}
}
}
}
return pre[T] != -1;
}
void MCMF () {
while (SPFA ()) {
mincost += dist[T] * flow[T];
int now = T;
while (now != S) {
edge[id[now]].flow -= flow[T];
edge[id[now] ^ 1].flow += flow[T];
now = pre[now];
}
}
printf ("%d\n", mincost);
}
int main () {
n = read (), S = 0, T = n + 1; int sum = 0;
for (register int i = 1; i <= n; i++) a[i] = read (), sum += a[i];
int ave = sum / n;
for (register int i = 1; i <= n; i++)
if (a[i] <= ave) connect (i, T, ave - a[i], 0), connect (T, i, 0, 0);
else connect (S, i, a[i] - ave, 0), connect (i, S, 0, 0);
for (register int i = 2; i <= n; i++) {
connect (i, i - 1, INF, 1), connect (i - 1, i, 0, -1),
connect (i - 1, i, INF, 1), connect (i, i - 1, 0, -1);
}
connect (1, n, INF, 1), connect (n, 1, 0, -1),
connect (n, 1, INF, 1), connect (1, n, 0, -1);
MCMF (); return 0;
}
魔术球问题 dinic
又是可以贪心的题,考虑如何建图a
注意到柱子是无关紧要的,显然考虑在小球上面做文章
拆点是一个重要技巧,一个小球拆成两个点,分别连向源点和汇点。
球内如何连边?对于当前编号
n
o
w
now
now,
i
i
i在
(
n
o
w
,
n
o
w
∗
2
)
(\sqrt {now},\sqrt{now*2})
(now,now∗2)内枚举,这样就可以直接找到形成完全平方数的小球,
i
2
−
n
o
w
i^2-now
i2−now向
n
o
w
<
<
1
∣
1
now<<1|1
now<<1∣1连边即可
如果某次dinic后发现没有新增的流(可以新放上去的小球)那么就新增一根柱子,柱子数大于n时退出。
dinic更新流量时,记录流向当前节点的是哪个点,最后从源点开始输出即可
我的F[]
数组记录的是这个编号的小球有没有被放过,Fst[]
数组记录的是这根柱子上的第一个球是什么
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#define MAXN 100010
#define INF 999999999
using namespace std;
struct fls {
int to, next, val;
}edge[MAXN << 1];
bool F[MAXN];
int head[MAXN << 1], pre[MAXN], vis[MAXN], level[MAXN], q[MAXN], Fst[MAXN];
int S = 100001, T = 100002, cnt = 1, n, tot, now, t, w;
inline int read () {
register int s = 0, w = 1;
register char ch = getchar ();
while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
return s * w;
}
inline void connect (int u, int v, int w) {
edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].next = head[u], head[u] = cnt;
}
inline bool BFS () {
memset (level, -1, sizeof level);
q[t = w = 1] = S, level[S] = 0;
while (t <= w) {
int now = q[t];
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].val != 0 && level[v] == -1) {
level[v] = level[now] + 1;
q[++w] = v;
}
}
t++;
}
return (level[T] != -1);
}
int DFS (int now, int flow) {
int ret = flow; if (now == T) return flow;
for (register int i = head[now]; i; i = edge[i].next) {
int v = edge[i].to; if (ret <= 0) break;
if (edge[i].val != 0 && level[v] == level[now] + 1) {
int o = DFS (v, min (ret, edge[i].val));
ret -= o, edge[i].val -= o, edge[i ^ 1].val += o;
if (o > 0) pre[now >> 1] = v >> 1;
}
}
return flow - ret;
}
inline void dinic () {
int ans = 0;
while (BFS ()) ans += DFS (S, INF);
if (! ans) Fst[++tot] = now;
}
int main () {
n = read ();
while (tot <= n) {
now++;
connect (S, now << 1, 1), connect (now << 1, S, 0),
connect (now << 1 | 1, T, 1), connect (T, now << 1 | 1, 0);
for (register int i = (int) sqrt (now) + 1; (i * i) < (now << 1); i++) {
connect ((i * i - now) << 1, now << 1 | 1, 1);
connect (now << 1 | 1, (i * i - now) << 1, 0);
}
dinic ();
}
printf ("%d\n", now - 1);
for (register int i = 1; i <= n; i++)
if (! F[Fst[i]]) {
for (register int j = Fst[i]; j && j != T >> 1; j = pre[j]) {
F[j] = 1, printf ("%d ", j);
}
puts ("");
}
return 0;
}