网络流求最大流Dinic 当前弧+无用点优化
Dinic算法
-
在EK算法(Edmonds-Karp)中,每次BFS后只找到一条增广路,Dinic算法每次可以找到多条,从而更加优秀
-
Dinic算法每次先BFS构造出分层图,再在分层图上Dfs找到增广路,在回溯时更新剩余容量
-
Dinic算法时间复杂度\(O(n^2m)\),但实际会跑的很快,一般1e5规模的都可以跑过去
当前弧优化
-
BFS之后每增广一条路,这条路就一定不会再可以到达汇点,下次就不再遍历这条边
-
体现在代码上就是吧head拷贝出来,Dfs的时候实时更新就好了
Code
- 主要还是代码,luogu板子题目地址
bool Bfs() {
memset(dep, 0, n * 4 + 4);
memcpy(hd, head, n * 4 + 4);//每次把head数组拷贝一遍
dep[s] = 1;
q[l=r=1] = s;
while (l <= r) {
int x = q[l++];
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!e[i].d || dep[y]) continue;//如果不能流或已经更新过就不再更新
dep[y] = dep[x] + 1; q[++r] = y;
if (y == t) return 1;
}
}
return 0;
}
int Dinic(int x, int lim) {//x是当前点,lim是限制
if (x == t) return lim;//返回的值才是真正的流量
int sum = 0;//记录总流量
for (int i = hd[x]; i && lim; i = e[i].next) {
hd[x] = i;//当前弧优化
int y = e[i].t;
if (!e[i].d || dep[y] != dep[x] + 1) continue;//只有在不同的层才进行增广
int f = Dinic(y, std::min(e[i].d, lim));
sum += f; lim -= f;
e[i].d -= f; e[i^1].d += f;
}
if (!sum) dep[x] = 0;//无用点优化
return sum;
}
int main() {
while (Bfs()) ans += Dinic(s, inf);//直到找不到增广路就停止
}
例题
[SCOI2007]蜥蜴
题目描述
- 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外。 每行每列中相邻石柱的距离为1,蜥蜴的跳跃距离是d,即蜥蜴可以跳到平面距离不超过d的任何一个石柱上。石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减1(如果仍然落在地图内部,则到达的石柱高度不变),如果该石柱原来高度为1,则蜥蜴离开后消失。以后其他蜥蜴不能落脚。任何时刻不能有两只蜥蜴在同一个石柱上。
输入格式
- 输入第一行为三个整数r,c,d,即地图的规模与最大跳跃距离。以下r行为石竹的初始状态,0表示没有石柱,1~3表示石柱的初始高度。以下r行为蜥蜴位置,“L”表示蜥蜴,“.”表示没有蜥蜴。
输出格式
- 输出仅一行,包含一个整数,即无法逃离的蜥蜴总数的最小值。
样例输入
5 8 2
00000000
02000000
00321100
02000000
00000000
........
........
..LLLL..
........
........
样例输出
1
数据范围与提示
100%的数据满足:1<=r, c<=20, 1<=d<=4
Solve
-
主要考察的是建图
-
首先想到给可以在两个可以到达的点连边,流量为两点高度的较小者,然后超级原点向有蜥蜴的点建流量为1的边,可以跳出边界的点向超级汇点连流量为inf的边
-
然而样例都过不去
-
仔细想想这样只能保证一条路上过的数量不会超过高度,但不能保证这个点不被经过更多次
-
解决办法就是将一个点拆成两个,一个入度点,一个出度点,入度点向出度点建一条流量为高度的边,其他的点之间建边的时候都是这个点的出度点向那个点的入度点建边,这样就能保证每个点只能被经过高度次
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 805;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar())
if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar())
x = x * 10 + c - '0';
return x * f;
}
struct Edge {
int next, t, d;
}e[N*31];
int head[N], edc = 1;
void Add(int x, int y, int z) {
e[++edc] = (Edge) {head[x], y, z};
head[x] = edc;
e[++edc] = (Edge) {head[y], x, 0};
head[y] = edc;
}
int n, m, s, t, d, h[25][25], hc, dep[N], hd[N], tot, q[N], l, r, ans;
char a[25][25], c[25];
bool Bfs() {
memset(dep, 0, t * 4 + 4);
memcpy(hd, head, t * 4 + 4);
dep[s] = 1;
q[l=r=1] = s;
while (l <= r) {
int x = q[l++];
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!e[i].d || dep[y]) continue;
dep[y] = dep[x] + 1, q[++r] = y;
if (y == t) return 1;
}
}
return 0;
}
int Dinic(int x, int lim) {
if (x == t) return lim;
int sum = 0;
for (int i = hd[x]; i && lim; i = e[i].next) {
hd[x] = i;
int y = e[i].t;
if (!e[i].d || dep[y] != dep[x] + 1) continue;
int f = Dinic(y, std::min(e[i].d, lim));
lim -= f, sum += f;
e[i].d -= f, e[i^1].d += f;
}
if (!sum) dep[x] = 0;
return sum;
}
int main() {
n = read(), m = read(); d = read();
s = 0, t = n * m * 2 + 1;
for (int i = 1; i <= n; ++i)
scanf("%s", a[i] + 1);
for (int i = 1; i <= n; ++i) {
scanf("%s", c + 1);
for (int j = 1; j <= m; ++j) {
h[i][j] = ++hc;
if (a[i][j] != '0') Add(h[i][j], h[i][j] + n * m, a[i][j] - '0');
if (c[j] == 'L') Add(s, h[i][j], 1), tot++;
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (a[i][j] == '0') continue;
bool g = 0;
for (int x = i - d; x <= i + d; ++x) {
for (int y = j - d; y <= j + d; ++y) {
if ((x - i) * (x - i) + (y - j) * (y - j) > d * d) continue;
if (x < 1 || x > n || y < 1 || y > m) {
if (!g) Add(h[i][j] + n * m, t, N), g = 1;
continue;
}
if (a[x][y] == '0') continue;
Add(h[i][j] + n * m, h[x][y], N);
}
}
}
}
while (Bfs()) ans += Dinic(s, N);
printf("%d\n", tot - ans);
return 0;
}