求最短路必经边

求最短路的必经边。

建出最短路图,这个图一定是一个 DAG ,然后 dp 求出从起点到每一个点和从每一个点到终点的路径种数。

对于一条边,如果 \(from(u)\times to(v)\) 的值等于起点到终点的路径条数,那么这条边必须经过。

同样类似的办法可以求割点和割边。

转载于 yhx's blog

题目描述

校园内有 |V| 个点,和 |E| 条单行道,scx 要从点 S 走到 T,因为她是班长 & 政治课代表 & 物理课代表,非常机智,走的一定是最短路,小 Z 想和 scx 有一次偶遇,按照套路他要假装不会做,请你计算出他可以埋伏在哪些地方可以偶遇 scx。

输入格式

第一行包含四个正整数 |V|, |E|, S, T,代表点数、边数、起点和终点。

接下来 |E| 行,每行包含三个正整数 u, v, w,表示有一条从 u 到 v,且长度为 w (即花费时间为 w) 的有向边。

输出格式

第一行输出一个整数 n,代表有 n 个点可以偶遇 (当然包括 S 和 T 啦)。

第二行输出 n 个整数 (从小到大),表示可以偶遇的点是这 n 个。

题解

这就是一个最短路必经点的裸题,用的方法不是特别难。

如果有一条边不属于任何最短路径,则这条边一定是不会经过的。

所以,可以将这些没有意义的边删去,这样就得到了一个最短路网。

图 G = (V, E) 的最短路网是它的一个生成子图 G' = (V, E'),满足对 。

(scx: 这些就是最短路必经边吗?)

这只是最短路可能通过的边,但不一定是必经的边。

因此,可以用动态规划或记忆化搜索,对任意一个点 v,计算出从点 S 到点 v 通过 G' 的路径条数 f(v) 和点 v 到点 T 通过 G' 的路径条数 g(v),并记 N = f(T) = g(S)。

对一个点 v,若满足 f(v)·g(v) = N,也就是说,在最短路网中,从 S 到 v 的路径共有 f(v) 条,而从 v 到 T 的路径共有 g(v) 条,由乘法原理,从 S 经过 v 到 T 的路径共有 f(v)·g(v) 条。而从 S 到 T 的路径总共只有 N = f(v)·g(v) 条,所以,每一条从 S 到 T 的最短路径都经过点 v,也就是说,点 v 是个最短路必经点。

(scx: 那必经边就是一条边 e = (u, v) 满足 f(u)·g(v) = N 了?)

完全正确,由 e = (u, v),显然有 f(v) ≥ f(u),所以 N ≥ f(v)·g(v) ≥ f(u)·g(v) = N,所以可以看出,u, v 均为最短路必经点。

其实,对于一般的有起点和终点的有向连通图,也可以这样 DP 求割点和桥。

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/priority_queue.hpp>
#define maxV 100034
#define maxE 682936
#define INF 0x3f3f3f3f
using namespace std;

struct edge{
    int u, v, w;
    bool cx;
    edge(int u0 = 0, int v0 = 0, int w0 = 0): u(u0), v(v0), w(w0), cx(false) {}
};

struct _{
    int to, dist;
    _ (int to0 = 0, int dist0 = 0): to(to0), dist(dist0) {}
    bool operator < (const _ &b) const {return dist > b.dist;}
};

typedef long long ll;
typedef __gnu_pbds :: priority_queue <_> prioque;

int V, E, si, ti;
int i, j, u, v, w;
int first1[maxV], first2[maxV], next1[maxE], next2[maxE];
int d1[maxV], d2[maxV];
ll c1[maxV], c2[maxV];
edge e[maxE];
prioque pq;

inline void addedge1(int u, int i){next1[i] = first1[u]; first1[u] = i;}
inline void addedge2(int u, int i){next2[i] = first2[u]; first2[u] = i;}

void Dijkstra(int start, int *d, int *first, int *next){
    int i; _ t;
    pq.push(_(start, 0));
    for(i = 1; i <= V; i++) d[i] = (i == start ? 0 : INF);
    while(!pq.empty()){
        t = pq.top(); pq.pop();
        for(i = first[t.to]; i; i = next[i])
            if(t.dist + e[i].w < d[e[i].v]){
                d[e[i].v] = t.dist + e[i].w;
                pq.push(_(e[i].v, d[e[i].v]));
            }
    }
}

void dfs(int x, int y, ll *c, int *first, int *next){
    int i;
    if(x == y){c[x] = 1; return;}
    c[x] = 0;
    for(i = first[x]; i; i = next[i])
        if(e[i].cx){
            if(c[e[i].v] < 0) dfs(e[i].v, y, c, first, next);
            (c[x] += c[e[i].v]) >= INF ? c[x] -= INF : 0;
        }
}

int main(){
    scanf("%d%d%d%d", &V, &E, &si, &ti);
    for(i = 1; i <= E; i++){
        scanf("%d%d%d", &u, &v, &w);
        e[(i << 1) - 1] = edge(u, v, w);
        e[i << 1] = edge(v, u, w);
        addedge1(u, (i << 1) - 1);
        addedge2(v, i << 1);
    }
    Dijkstra(si, d1, first1, next1);
    Dijkstra(ti, d2, first2, next2);
    for(i = 1; i <= E; i++){
        j = (i << 1) - 1;
        e[j].cx = e[j + 1].cx = (d1[e[j].u] + e[j].w + d2[e[j].v] == d1[ti]);
    }
    memset(c1, -1, sizeof c1);
    memset(c2, -1, sizeof c2);
    dfs(si, ti, c2, first1, next1);
    dfs(ti, si, c1, first2, next2);
    for(j = 0, i = 1; i <= V; i++)
        if(c1[i] >= 0 && c2[i] >= 0 && c1[i] * c2[i] % INF == c1[ti])
            d1[++j] = i;
    printf("%d\n", j);
    for(i = 1; i <= j; i++)
        printf("%d%c", d1[i], (i == j ? '\n' : ' '));
    return 0;
}

这道题还是不怎么坑的,主要是这么两点。

坑1:虽然这个图 G 是有向图,但是还要保存两倍边,因为要顺着跑一遍再倒着跑一遍 Dijkstra。

坑2:题解中的 f, g 函数值可能会比较大,所以最好取一个模 (但是有一定被卡风险,可以多取几个互素的)。

posted @ 2021-04-22 15:59  __Anchor  阅读(363)  评论(0编辑  收藏  举报