• 《算法竞赛进阶指南》 #0x61 图论


    题目链接:https://www.acwing.com/activity/content/punch_the_clock/6/

    最短路有很多种解法:

    BFS

    只适用于边权为1的图。

    DP

    只适用于DAG的解法,当一个点不存在入边,那么他的答案是可以立刻确定的。这个算法除了DAG以外没有任何要求,可以跑带负权的图。

    Dijkstra

    高效、稳定的单源最短路算法,适用于处理非负权边的情况。

    复杂度为: (O((n+m)log(n+m)))

    int n;
    vector<pii> G[MAXN + 5];
    
    bool vis[MAXN + 5];
    int dis[MAXN + 5];
    priority_queue<pii> PQ;
    
    void Dijkstra(int s) {    
        while(!PQ.empty())
            PQ.pop();
        memset(vis, 0, sizeof(vis[0]) * (n + 1));
        memset(dis, INF, sizeof(dis[0]) * (n + 1));
        dis[s] = 0;
        PQ.push({-dis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(auto &e : G[u]) {
                int v = e.first, w = e.second;
                if(dis[u] + w < dis[v]) {
                    dis[v] = dis[u] + w;
                    PQ.push({-dis[v], v});
                }
            }
        }
        return;
    }
    

    常见的技巧有:

    反向建图:源点为整个点击,汇点却只有单个,这时可能把边反向。

    分层建图:指定某些边可以使用一些特殊性质,比如可以使用一些魔法降低边的权值,但是魔法力量有限。这种问题一般魔法力量的范围不会太大,刚刚好可以把原图的每个点都拆成魔法力量的范围这么多,注意提取边的公共性质来减少边的存储。

    多个点同时作为源点:区别于多源最短路,这里求源点集到每个点的最短路,可以建一个超级源点,然后向源点集连权值为0的边,或者直接把整个点集都全部丢到 PQ 里面。

    单源单汇:类似双向BFS,退出的时候会快一点。或者在汇点被push从PQ中pop出去的同时退出也可以加速。

    双端BFS

    又称0-1BFS,使用双端队列代替队列的BFS,适用于边权值只有0和1的图,也可以理解为手动控制优先队列的Dijkstra算法(事实上这几个都没什么区别)。

    复杂度为: (O(n+m))

    vector<pii> G[MAXN + 5];
    
    int dis[MAXN + 5];
    
    deque<int> DQ;
    void BFS(int s, int Limit) {
        DQ.clear();
        memset(dis, INF, sizeof(dis[0]) * (n + 1));
        dis[s] = 0;
        DQ.push_back(s);
        while(!DQ.empty()) {
            int u = DQ.front();
            DQ.pop_front();
            if(u == n)
                break;
            for(auto &e : G[u]) {
                int v = e.first, w = e.second;
                if(w == 0) {
                    if(dis[u] < dis[v]) {
                        dis[v] = dis[u];
                        DQ.push_front(v);
                    }
                } else {
                    if(dis[u] + 1 < dis[v]) {
                        dis[v] = dis[u] + 1;
                        DQ.push_back(v);
                    }
                }
            }
        }
        return;
    }
    

    技巧:

    有可能可以把一些问题转换成0-1BFS来解决,例如下面的340,是求最大值+分层建图,因为可以二分最大值,然后根据与最大值的关系来区分边权是0还是1,分层建图的同时就是进行BFS的过程。

    SPFA

    又称“队列优化的Bellman-Ford算法”,复杂度是很假的,最坏可以退化到 (O(nm)) 。适用于任何图,若不存在从源点出发能到达的负环,则SPFA可以计算出单源最短路,否则会在同一个节点入队超过n次之后报告存在负环。

    Floyd

    实现非常简单的,任意两点间的最短路,也可以正确检测出负环。(Floyd结束后存在自己到自己的最短路为负数的点,那么可以无限绕圈圈)缺点是复杂度过大。只适用于很小的图。

    340. 通信线路

    题意:在郊区有 N 座通信基站,P 条 双向电缆,第 i 条电缆连接基站 Ai 和 Bi。特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li 。电话公司正在举行优惠活动。农场主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。求至少用多少钱可以完成升级。

    数据范围:

    0≤K<N≤1000,
    1≤P≤10000,
    1≤Li≤1000000

    题解:

    分层图Dijkstra

    从特殊到一般,先考虑 K=0 的情形,这个时候是要找一条最短路,只不过不是加法运算而是 max 运算,用 Dijkstra 算法可以直接求解。注意到好像以前做过这类题目(貌似第一次见是2018年多校)都从分层图去考虑。观察一下数据范围,确实可以建分层图。

    把一个点拆成 K+1 个点,二元组 (id,k) 表示编号为 id 的点已经使用了 k 次免费的状态,那么原本的边也拆成超级多条边,不过非常容易注意到这些边可以高度压缩。

    复杂度为 (O(n*k*log(n*k)))

    vector写法:

    const int MAXNK = 1000 * 1001;
    
    int n, p, k;
    vector<pii> G[MAXNK + 5];
    
    int id_ki_pos(int id, int ki) {
        return (id - 1) * (k + 1) + ki + 1;
    }
    
    int pos_id(int pos) {
        return (pos - 1) / (k + 1) + 1;
    }
    
    int pos_ki(int pos) {
        return (pos - 1) % (k + 1);
    }
    
    bool vis[MAXNK + 5];
    int dis[MAXNK + 5];
    priority_queue<pii> PQ;
    
    void Dijkstra(int sid) {
        while(!PQ.empty())
            PQ.pop();
        memset(vis, 0, sizeof(vis[0]) * (n + 1) * (k + 1));
        memset(dis, INF, sizeof(dis[0]) * (n + 1) * (k + 1));
        int spos = id_ki_pos(sid, 0);
        dis[spos] = 0;
        PQ.push({-dis[spos], spos});
        while(!PQ.empty()) {
            int upos = PQ.top().second;
            PQ.pop();
            if(vis[upos])
                continue;
            vis[upos] = 1;
            int uid = pos_id(upos);
            int uki = pos_ki(upos);
            if(uid == n)
                break;
            for(auto &e : G[uid]) {
                int vid = e.first, w = e.second;
                {
                    int v0pos = id_ki_pos(vid, uki);
                    if(max(dis[upos], w) < dis[v0pos]) {
                        dis[v0pos] = max(dis[upos], w);
                        PQ.push({-dis[v0pos], v0pos});
                    }
                }
                if(uki < k) {
                    int v1pos = id_ki_pos(vid, uki + 1);
                    if(max(dis[upos], 0) < dis[v1pos]) {
                        dis[v1pos] = max(dis[upos], 0);
                        PQ.push({-dis[v1pos], v1pos});
                    }
                }
            }
        }
        return;
    }
    
    void TestCase() {
        scanf("%d%d%d", &n, &p, &k);
        for(int i = 1; i <= n; ++i)
            G[i].clear();
        for(int i = 1; i <= p; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            G[u].push_back({v, w});
            G[v].push_back({u, w});
        }
        Dijkstra(1);
        int ans = INF;
        for(int ki = 0; ki <= k; ++ki)
            ans = min(ans, dis[id_ki_pos(n, ki)]);
        if(ans == INF)
            ans = -1;
        printf("%d
    ", ans);
        return;
    }
    

    链式前向星写法:

    const int MAXNK = 1000 * 1001;
    const int MAXP = 1000000;
    
    int n, p, k;
    
    int G[MAXNK + 5];
    struct Edge {
        int v, w, nxt;
        Edge() {}
        Edge(int v, int w, int nxt): v(v), w(w), nxt(nxt) {}
    } edge[MAXP * 2 + 5];
    int top;
    
    void Init() {
        top = 0;
        memset(G, -1, sizeof(G[0]) * (n + 1));
    }
    
    void AddEdge(int u, int v, int w) {
        ++top;
        edge[top] = Edge(v, w, G[u]);
        G[u] = top;
    }
    
    int id_ki_pos(int id, int ki) {
        return (id - 1) * (k + 1) + ki + 1;
    }
    
    int pos_id(int pos) {
        return (pos - 1) / (k + 1) + 1;
    }
    
    int pos_ki(int pos) {
        return (pos - 1) % (k + 1);
    }
    
    bool vis[MAXNK + 5];
    int dis[MAXNK + 5];
    priority_queue<pii> PQ;
    
    void Dijkstra(int sid) {
        while(!PQ.empty())
            PQ.pop();
        memset(vis, 0, sizeof(vis[0]) * (n + 1) * (k + 1));
        memset(dis, INF, sizeof(dis[0]) * (n + 1) * (k + 1));
        int spos = id_ki_pos(sid, 0);
        dis[spos] = 0;
        PQ.push({-dis[spos], spos});
        while(!PQ.empty()) {
            int upos = PQ.top().second;
            PQ.pop();
            if(vis[upos])
                continue;
            vis[upos] = 1;
            int uid = pos_id(upos);
            int uki = pos_ki(upos);
            if(uid == n)
                break;
            for(int eid = G[uid]; eid != -1; eid = edge[eid].nxt) {
                int vid = edge[eid].v, w = edge[eid].w;
                {
                    int v0pos = id_ki_pos(vid, uki);
                    if(max(dis[upos], w) < dis[v0pos]) {
                        dis[v0pos] = max(dis[upos], w);
                        PQ.push({-dis[v0pos], v0pos});
                    }
                }
                if(uki < k) {
                    int v1pos = id_ki_pos(vid, uki + 1);
                    if(max(dis[upos], 0) < dis[v1pos]) {
                        dis[v1pos] = max(dis[upos], 0);
                        PQ.push({-dis[v1pos], v1pos});
                    }
                }
            }
        }
        return;
    }
    
    void TestCase() {
        scanf("%d%d%d", &n, &p, &k);
        Init();
        for(int i = 1; i <= p; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            AddEdge(u, v, w);
            AddEdge(v, u, w);
        }
        Dijkstra(1);
        int ans = INF;
        for(int ki = 0; ki <= k; ++ki)
            ans = min(ans, dis[id_ki_pos(n, ki)]);
        if(ans == INF)
            ans = -1;
        printf("%d
    ", ans);
        return;
    }
    

    收获:

    1. 这些下标比较烦人,要先在纸上画清楚。
    2. 边数比较大,层数也比较大,全部加边容易卡空间,我看见那个全部加边的写法简直是扯淡。
    3. 链式前向星的速度比vector显著快,大概快6倍,极有可能是没有打开O2优化。

    二分+双端BFS

    题目要求的是最小化最大值,是一种常见的二分+验证的题目,假如二分枚举一个最大值maxw,只需要把所有<=maxw的边记作cost=0(不使用免费次数),而把>maxw的边记作cost=1(使用1次免费次数),变成经典的0-1BFS问题,用双端队列解决,cost=1的从队尾加入,cost=0的从队首加入。

    实际证明这个算法是最快的,也非常省空间,因为双端BFS常数极小,且验证速度极快(验证速度 (O(n)) )。

    复杂度为 (O(n*log MAXL))

    const int MAXN = 1000;
    const int MAXP = 1000000;
    
    int n, p, k;
    
    int G[MAXN + 5];
    struct Edge {
        int v, w, nxt;
        Edge() {}
        Edge(int v, int w, int nxt): v(v), w(w), nxt(nxt) {}
    } edge[MAXP * 2 + 5];
    int top;
    
    void Init() {
        top = 0;
        memset(G, -1, sizeof(G[0]) * (n + 1));
    }
    
    void AddEdge(int u, int v, int w) {
        ++top;
        edge[top] = Edge(v, w, G[u]);
        G[u] = top;
    }
    
    int dis[MAXN + 5];
    
    deque<int> DQ;
    bool BFS(int s, int Limit) {
        DQ.clear();
        memset(dis, INF, sizeof(dis[0]) * (n + 1));
        dis[s] = 0;
        DQ.push_back(s);
        while(!DQ.empty()) {
            int u = DQ.front();
            DQ.pop_front();
            if(u == n)
                break;
            for(int eid = G[u]; eid != -1; eid = edge[eid].nxt) {
                int v = edge[eid].v, w = edge[eid].w;
                if(w <= Limit) {
                    if(dis[u] < dis[v]) {
                        dis[v] = dis[u];
                        DQ.push_front(v);
                    }
                } else {
                    if(dis[u] + 1 < dis[v]) {
                        dis[v] = dis[u] + 1;
                        DQ.push_back(v);
                    }
                }
            }
        }
        return dis[n] <= k;
    }
    
    void TestCase() {
        scanf("%d%d%d", &n, &p, &k);
        Init();
        for(int i = 1; i <= p; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            AddEdge(u, v, w);
            AddEdge(v, u, w);
        }
        int L = 0, R = INF, ans;
        while(1) {
            int M = (L + R) >> 1;
            if(L == M) {
                if(BFS(1, L)) {
                    ans = L;
                    break;
                }
                ans = R;
                break;
            }
            if(BFS(1, M))
                R = M;
            else
                L = M + 1;
        }
        if(ans == INF)
            ans = -1;
        printf("%d
    ", ans);
        return;
    }
    

    收获:

    1. 最小化最大值的问题,可以尝试转化成二分枚举+快速验证。
    2. 不要搞错二分的值域,这里从0开始。
    3. 二分时初始R为无穷大,二分达到无穷大说明无解。
    4. BFS不需要vis数组。

    341. 最优贸易

    题意:C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。商人阿龙来到C国旅游。当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。

    请你告诉阿龙,他最多能赚取多少旅费。

    数据范围:

    1≤n≤100000,
    1≤m≤500000,
    1≤各城市水晶球价格≤100

    题解:因为各个城市可以反复经过,所以问题变成了这样:从1号城市出发,到达x号城市,购买一个水晶球;从x号城市出发,到达y号城市,卖出一个水晶球;从y号城市出发,到达n号城市。看一下数据,水晶球的价格貌似是突破口?

    假算法:这道题有个非常明显的强连通分量的解法,首先把所有的强连通分量缩点,记录每个新点的最大值、最小值,然后变成一个DAG。再从新图中包含原图的1号点为源点找出到其他点的路径上的最小值(Dijkstra),然后从新图中包含原图的n号点为汇点找出其他点到汇点的路径上的最大值(反向图上Dijkstra),然后对遍历一遍差取最大值。

    const int MAXN = 1e5;
    
    int n, m;
    int price[MAXN + 5];
    
    namespace SCC {
        int n;
        vector<int> G[MAXN + 5], BG[MAXN + 5];
    
        int c1[MAXN + 5], cntc1;
        int c2[MAXN + 5], cntc2;
        int s[MAXN + 5], cnts;
    
        int n2;
        vector<int> V2[MAXN + 5];
        vector<int> G2[MAXN + 5], BG2[MAXN + 5];
    
        void Init(int _n) {
            n = _n;
            cntc1 = 0, cntc2 = 0, cnts = 0;
            for(int i = 1; i <= n; ++i) {
                G[i].clear();
                BG[i].clear();
                c1[i] = 0;
                c2[i] = 0;
                s[i] = 0;
                V2[i].clear();
                G2[i].clear();
                BG2[i].clear();
            }
            return;
        }
    
        void AddEdge(int u, int v) {
            G[u].push_back(v);
            BG[v].push_back(u);
            return;
        }
    
        void dfs1(int u) {
            c1[u] = cntc1;
            for(auto &v : G[u]) {
                if(!c1[v])
                    dfs1(v);
            }
            s[++cnts] = u;
        }
    
        void dfs2(int u) {
            V2[cntc2].push_back(u);
            c2[u] = cntc2;
            for(auto &v : BG[u]) {
                if(!c2[v])
                    dfs2(v);
            }
            return;
        }
    
        void Kosaraju() {
            for(int i = 1; i <= n; ++i) {
                if(!c1[i]) {
                    ++cntc1;
                    dfs1(i);
                }
            }
            for(int i = n; i >= 1; --i) {
                if(!c2[s[i]]) {
                    ++cntc2;
                    dfs2(s[i]);
                }
            }
            return;
        }
    
        void Build() {
            n2 = cntc2;
            for(int i = 1; i <= n2; ++i) {
                for(auto &u : V2[i]) {
                    for(auto &v : G[u]) {
                        if(c2[v] != i) {
                            G2[i].push_back(c2[v]);
                            BG2[c2[v]].push_back(i);
                        }
                    }
                }
            }
            for(int i = 1; i <= n2; ++i) {
                sort(G2[i].begin(), G2[i].end());
                G2[i].erase(unique(G2[i].begin(), G2[i].end()), G2[i].end());
                sort(BG2[i].begin(), BG2[i].end());
                BG2[i].erase(unique(BG2[i].begin(), BG2[i].end()), BG2[i].end());
            }
            return;
        }
    
        int minV2[MAXN + 5], maxV2[MAXN + 5];
    
        bool vis[MAXN + 5];
        priority_queue<pii> PQ;
    
        int mindis[MAXN + 5];
    
        void DijkstraMin(int s) {
            while(!PQ.empty())
                PQ.pop();
            memset(vis, 0, sizeof(vis[0]) * (n2 + 1));
            memset(mindis, INF, sizeof(mindis[0]) * (n2 + 1));
            mindis[s] = minV2[s];
            PQ.push({-mindis[s], s});
            while(!PQ.empty()) {
                int u = PQ.top().second;
                PQ.pop();
                if(vis[u])
                    continue;
                vis[u] = 1;
                for(auto &v : G2[u]) {
                    if(mindis[u] < mindis[v]) {
                        mindis[v] = min(minV2[v], mindis[u]);
                        PQ.push({-mindis[v], v});
                    }
                }
            }
            return;
        }
    
        int maxdis[MAXN + 5];
    
        void DijkstraMax(int s) {
            while(!PQ.empty())
                PQ.pop();
            memset(vis, 0, sizeof(vis[0]) * (n2 + 1));
            memset(maxdis, -INF, sizeof(maxdis[0]) * (n2 + 1));
            maxdis[s] = maxV2[s];
            PQ.push({maxdis[s], s});
            while(!PQ.empty()) {
                int u = PQ.top().second;
                PQ.pop();
                if(vis[u])
                    continue;
                vis[u] = 1;
                for(auto &v : BG2[u]) {
                    if(maxdis[u] > maxdis[v]) {
                        maxdis[v] = max(maxV2[v], maxdis[u]);
                        PQ.push({maxdis[v], v});
                    }
                }
            }
            return;
        }
    
        void Solve() {
            for(int i = 1; i <= n2; ++i) {
                minV2[i] = INF, maxV2[i] = -INF;
                for(auto &u : V2[i]) {
                    minV2[i] = min(minV2[i], price[u]);
                    maxV2[i] = max(maxV2[i], price[u]);
                }
            }
            DijkstraMin(c2[1]);
            DijkstraMax(c2[n]);
            int ans = 0;
            for(int i = 1; i <= n2; ++i)
                ans = max(ans, maxdis[i] - mindis[i]);
            printf("%d
    ", ans);
            return;
        }
    }
    
    void TestCase() {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &price[i]);
        SCC::Init(n);
        for(int i = 1; i <= m; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            SCC::AddEdge(u, v);
            if(w == 2)
                SCC::AddEdge(v, u);
        }
        SCC::Kosaraju();
        SCC::Build();
        SCC::Solve();
        return;
    }
    

    注意点权Dijkstra也是要先初始化为无穷(而不是对应的点权),然后在vis节点(可以在入PQ的时候,也可以在出PQ的时候,在入PQ的时候更新理论上会更快)的时候才把对应的点权更新上去。因为dis[u]的含义是“从s点到u点经过的所有点的点权的最小值”。

    上面这个做法会不会是假做法呢?好像是挺假的,群友们也说DAG上跑Dijkstra有问题?我想到了一个反例:

    5 5
    5 4 2 1 3
    1 2 1
    1 3 1
    2 4 1
    4 3 1
    3 5 1
    

    这样缩点之后也是完全一样的图,Dijkstra的过程是这样:

    取出1号点,把{2,3号}和{4,2号}加入PQ;
    取出3号点,把{2,5号}加入PQ;
    取出5号点,更新了错误的最小值为2。(因为1->2->4->5才是最小值1,在4号点取得最小值)。

    不过虽然Dijkstra跑出了错误的结果,但是我这个假算法的鲁棒性也太强了吧。

    构造一个这样的图:

    生成好看的图的工具是:https://csacademy.com/app/graph_editor/

    这个图里面,计算从40开始的最小价格,Dijkstra会直接把50标记为40,而不是正确的10。反过来计算到达50的最大价格,又会把10标记成20,而不是正确的90。原因是在于Dijkstra被局部的最优情况欺骗了。

    对应的数据是:

    7 8
    40 100 10 20 90 15 50
    1 2
    1 7
    2 3
    3 4
    3 5
    4 7
    5 6
    6 7
    

    但是好像还是没卡掉,这个假算法的鲁棒性也太强了,因为他甚至会更新已经vis过的节点!也就是说Dijkstra去掉那个跳过已vis节点会更加鲁棒?但是这个算法确实是错的,只需要在正确答案更新其后继节点之前,保证后继节点已经被vis过,那么后继节点的新信息就不会继续传递给其后继节点。

    但是缩点之后DP是肯定没有错的,当一个点已经没有任何入边,那么他不可能再进行修改,他的答案也随之确定。

    const int MAXN = 1e5;
    
    int n, m;
    int price[MAXN + 5];
    
    namespace SCC {
        int n;
        vector<int> G[MAXN + 5], BG[MAXN + 5];
    
        int c1[MAXN + 5], cntc1;
        int c2[MAXN + 5], cntc2;
        int s[MAXN + 5], cnts;
    
        int n2;
        vector<int> V2[MAXN + 5];
        vector<int> G2[MAXN + 5], BG2[MAXN + 5];
    
        void Init(int _n) {
            n = _n;
            cntc1 = 0, cntc2 = 0, cnts = 0;
            for(int i = 1; i <= n; ++i) {
                G[i].clear();
                BG[i].clear();
                c1[i] = 0;
                c2[i] = 0;
                s[i] = 0;
                V2[i].clear();
                G2[i].clear();
                BG2[i].clear();
            }
            return;
        }
    
        void AddEdge(int u, int v) {
            G[u].push_back(v);
            BG[v].push_back(u);
            return;
        }
    
        void dfs1(int u) {
            c1[u] = cntc1;
            for(auto &v : G[u]) {
                if(!c1[v])
                    dfs1(v);
            }
            s[++cnts] = u;
        }
    
        void dfs2(int u) {
            V2[cntc2].push_back(u);
            c2[u] = cntc2;
            for(auto &v : BG[u]) {
                if(!c2[v])
                    dfs2(v);
            }
            return;
        }
    
        void Kosaraju() {
            for(int i = 1; i <= n; ++i) {
                if(!c1[i]) {
                    ++cntc1;
                    dfs1(i);
                }
            }
            for(int i = n; i >= 1; --i) {
                if(!c2[s[i]]) {
                    ++cntc2;
                    dfs2(s[i]);
                }
            }
            return;
        }
    
        void Build() {
            n2 = cntc2;
            for(int i = 1; i <= n2; ++i) {
                for(auto &u : V2[i]) {
                    for(auto &v : G[u]) {
                        if(c2[v] != i) {
                            G2[i].push_back(c2[v]);
                            BG2[c2[v]].push_back(i);
                        }
                    }
                }
            }
            for(int i = 1; i <= n2; ++i) {
                sort(G2[i].begin(), G2[i].end());
                G2[i].erase(unique(G2[i].begin(), G2[i].end()), G2[i].end());
                sort(BG2[i].begin(), BG2[i].end());
                BG2[i].erase(unique(BG2[i].begin(), BG2[i].end()), BG2[i].end());
            }
            return;
        }
    
        int minV2[MAXN + 5], maxV2[MAXN + 5];
        int indeg[MAXN + 5], outdeg[MAXN + 5];
        int vis1[MAXN + 5], visn[MAXN + 5];
        queue<int> Q;
    
        void DPin() {
            while(!Q.empty())
                Q.pop();
            for(int i = 1; i <= n2; ++i) {
                vis1[i] = 0;
                indeg[i] = BG2[i].size();
                if(indeg[i] == 0)
                    Q.push(i);
            }
            while(!Q.empty()) {
                int u = Q.front();
                Q.pop();
                if(c2[1] == u)
                    vis1[u] = 1;
                for(auto &v : G2[u]) {
                    --indeg[v];
                    if(indeg[v] == 0)
                        Q.push(v);
                    if(vis1[u] == 1) {
                        minV2[v] = min(minV2[v], minV2[u]);
                        vis1[v] = 1;
                    }
                }
            }
        }
    
        void DPout() {
            while(!Q.empty())
                Q.pop();
            for(int i = 1; i <= n2; ++i) {
                visn[i] = 0;
                outdeg[i] = G2[i].size();
                if(outdeg[i] == 0)
                    Q.push(i);
            }
            while(!Q.empty()) {
                int u = Q.front();
                Q.pop();
                if(c2[n] == u)
                    visn[u] = 1;
                for(auto &v : BG2[u]) {
                    --outdeg[v];
                    if(outdeg[v] == 0)
                        Q.push(v);
                    if(visn[u] == 1) {
                        maxV2[v] = max(maxV2[v], maxV2[u]);
                        visn[v] = 1;
                    }
                }
            }
        }
    
        void Solve() {
            for(int i = 1; i <= n2; ++i) {
                minV2[i] = INF, maxV2[i] = -INF;
                for(auto &u : V2[i]) {
                    minV2[i] = min(minV2[i], price[u]);
                    maxV2[i] = max(maxV2[i], price[u]);
                }
                indeg[i] = BG2[i].size();
                outdeg[i] = G2[i].size();
            }
            DPin();
            DPout();
            int ans = 0;
            for(int i = 1; i <= n2; ++i) {
                if(vis1[i] == 1 && visn[i] == 1)
                    ans = max(ans, maxV2[i] - minV2[i]);
            }
            printf("%d
    ", ans);
            return;
        }
    }
    
    void TestCase() {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &price[i]);
        SCC::Init(n);
        for(int i = 1; i <= m; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            SCC::AddEdge(u, v);
            if(w == 2)
                SCC::AddEdge(v, u);
        }
        SCC::Kosaraju();
        SCC::Build();
        SCC::Solve();
        return;
    }
    

    或者用直接进行正反两次SPFA就可以了,毕竟这个假算法简单又无脑。

    
    

    收获:

    1. 假算法甚至也可以通过题目,没招的话可以试试看假一波,假如不是对着我的假算法卡,非常难卡。
    2. 居然没有故意卡SPFA,正式区域赛不到没招不要上SPFA。
  • 相关阅读:
    Java命名规范
    用U盘安装系统2
    将Sublime Text3添加到右键菜单中
    Git版本控制使用介绍
    Sublime Text各种插件使用方法
    安装Sublime Text 3插件
    Sublime Text修改显示图标
    卸载Visual Studio Code后删除右键Open with Code…
    做最好的自己(Be Your Personal Best)
    Eclipse全屏及插件下载
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12667294.html
Copyright © 2020-2023  润新知