题目地址:
https://www.acwing.com/problem/content/2238/
伊基是一个小国 – 凤凰国的国王。凤凰国是如此之小,以至于只有一个城市负责日常商品的生产,并使用公路网将商品运送到首都。伊基发现本国最大的问题在于运输速度太慢了。因为伊基以前是ACM/ICPC的参赛者,他意识到这其实是一个最大流问题。他编写了一个最大流程序,并计算出了当前运输网络的最大运输能力。他对运输速度的现状十分不满,并希望能够提高国家的运输能力。提高运输能力的方法很简单,伊基将在运输网络中重建一些道路,以使这些道路具有更高的运输能力。但是不幸的是,凤凰国的财力有限,道路建设经费只够重建一条道路。伊基想要知道共有多少条道路可以纳入重建道路候选名单。这些道路需要满足,将其重建后,国家的总运输能力能够增加。
输入格式:
第一行包含
N
N
N和
M
M
M,分别表示城市和道路的数量。接下来
M
M
M行,每行包含三个整数
a
,
b
,
c
a,b,c
a,b,c,表示存在一条道路从城市
a
a
a通往城市
b
b
b,且运输能力为
c
c
c。所有道路都是有方向的。城市编号从
0
0
0到
N
−
1
N−1
N−1。生产日常商品的城市为
0
0
0号城市,首都为
N
−
1
N−1
N−1号城市。
输出格式:
输出一个整数
K
K
K,表示存在
K
K
K条道路,对其中每条道路进行重建都会增加运输网络的运输能力。
数据范围:
1
≤
N
≤
500
1≤N≤500
1≤N≤500
1
≤
M
≤
5000
1≤M≤5000
1≤M≤5000
0
≤
a
,
b
<
N
0≤a,b<N
0≤a,b<N
0
≤
c
≤
100
0≤c≤100
0≤c≤100
其实就是问有多少条边,如果其容量变大,就能找到增广路。设我们已经求出了一个最大流与该流对应的残留网络,我们证明,这样的边
u
→
v
u\to v
u→v符合条件当且仅当其流量满了,并且存在从源点
s
s
s到
u
u
u的“部分增广路”(意思是路径边权全是正的的路径),也存在从
v
v
v到汇点
t
t
t的“部分增广路”。
1、如果
u
→
v
u\to v
u→v容量变大就能求出更大流,那么说明这条边容量变大之后,必定在某条增广路上。并且,这条边在最大流里一定是满的,不然的话,设其不满,但是将其容量变大能求得更大流,这说明找到了一条经过这条边的增广路,并且增广的流量可以使得经过这条边的流大于原容量(如果不大于原容量的话,那说明最大流的残留网络里有增广路,这与流最大矛盾)。但是我们总可以让增广流的流量变小一点,使得这条边上的流小于等于原容量,这也与流最大矛盾了。所以必要性成立;
2、充分性显然。
所以具体算法就是,先求最大流,然后找到流量满了的边 u → v u\to v u→v,看一下哪些边满足既存在从 s s s到 u u u的部分增广路,也存在从 v v v到 t t t的部分增广路。代码实现方面,可以在Dinic完之后,用DFS预处理一下所有从 s s s能沿着部分增广路到达的点,和能沿着部分增广路到达 t t t的点。由于我们总是在残留网络里操作的,所以第一部分点,可以直接从 s s s沿着正向边出发寻找;而第二部分点,则要从 t t t沿着边的箭头的反方向走。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 5010 * 2, INF = 1e8;
int n, m, S, T;
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];
bool vis_s[N], vis_t[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx++;
e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx++;
}
bool bfs() {
memset(d, -1, sizeof d);
int hh = 0, tt = 0;
q[tt++] = S, d[S] = 0, cur[S] = h[S];
while (hh < tt) {
int t = q[hh++];
for (int i = h[t]; ~i; i = ne[i]) {
int v = e[i];
if (d[v] == -1 && f[i]) {
d[v] = d[t] + 1;
if (v == T) return true;
cur[v] = h[v];
q[tt++] = v;
}
}
}
return false;
}
int dfs(int u, int limit) {
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {
cur[u] = i;
int v = e[i];
if (d[v] == d[u] + 1 && f[i]) {
int t = dfs(v, min(limit - flow, f[i]));
if (!t) d[v] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int r = 0, flow;
while (bfs()) while (flow = dfs(S, INF)) r += flow;
return r;
}
void dfs2(int u, bool st[], int t) {
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
// 如果t = 0则是找的就是边i,如果t = 1则是找的是i的反向边
int j = i ^ t;
int v = e[i];
// 增广路需要容量大于0
if (f[j] && !st[v])
dfs2(v, st, t);
}
}
int main() {
scanf("%d%d", &n, &m);
S = 0, T = n - 1;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
dinic();
// 0代表沿着边箭头方向走,1代表沿着边箭头反方向走
dfs2(S, vis_s, 0);
dfs2(T, vis_t, 1);
int res = 0;
// 只枚举原图的边,即残留网络的正向边
for (int i = 0; i < m * 2; i += 2)
// 容量为0说明满了,e[i ^ 1]是边的起点,e[i]是边的终点
if (!f[i] && vis_s[e[i ^ 1]] && vis_t[e[i]])
res++;
printf("%d", res);
return 0;
}
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m),空间 O ( n ) O(n) O(n)。