【ACWing】2236. 伊基的故事 I - 道路重建

题目地址:

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 N1。生产日常商品的城市为 0 0 0号城市,首都为 N − 1 N−1 N1号城市。

输出格式:
输出一个整数 K K K,表示存在 K K K条道路,对其中每条道路进行重建都会增加运输网络的运输能力。

数据范围:
1 ≤ N ≤ 500 1≤N≤500 1N500
1 ≤ M ≤ 5000 1≤M≤5000 1M5000
0 ≤ a , b < N 0≤a,b<N 0a,b<N
0 ≤ c ≤ 100 0≤c≤100 0c100

其实就是问有多少条边,如果其容量变大,就能找到增广路。设我们已经求出了一个最大流与该流对应的残留网络,我们证明,这样的边 u → v u\to v uv符合条件当且仅当其流量满了,并且存在从源点 s s s u u u的“部分增广路”(意思是路径边权全是正的的路径),也存在从 v v v到汇点 t t t的“部分增广路”。
1、如果 u → v u\to v uv容量变大就能求出更大流,那么说明这条边容量变大之后,必定在某条增广路上。并且,这条边在最大流里一定是满的,不然的话,设其不满,但是将其容量变大能求得更大流,这说明找到了一条经过这条边的增广路,并且增广的流量可以使得经过这条边的流大于原容量(如果不大于原容量的话,那说明最大流的残留网络里有增广路,这与流最大矛盾)。但是我们总可以让增广流的流量变小一点,使得这条边上的流小于等于原容量,这也与流最大矛盾了。所以必要性成立;
2、充分性显然。

所以具体算法就是,先求最大流,然后找到流量满了的边 u → v u\to v uv,看一下哪些边满足既存在从 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值