【ACWing】345. 牛站

题目地址:

https://www.acwing.com/problem/content/347/

给定一张由 T T T条边构成的无向图,点的编号为 1 ∼ 1000 1∼1000 11000之间的整数。求从起点 S S S到终点 E E E恰好经过 N N N条边(可以重复经过)的最短路。注意:数据保证一定有解。

输入格式:
1 1 1行:包含四个整数 N , T , S , E N,T,S,E N,T,S,E。第 2... T + 1 2...T+1 2...T+1行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。

输出格式:
输出一个整数,表示最短路的长度。

数据范围:
2 ≤ T ≤ 100 2≤T≤100 2T100
2 ≤ N ≤ 1 0 6 2≤N≤10^6 2N106

这题边数只有 100 100 100,而点的编号却能达到 1000 1000 1000,所以需要先离散化一下顶点编号,可以用哈希表来做。

首先,恰好经过 1 1 1条边的最短路邻接方阵是容易的,实际上它就是输入。设 g g g n × n n\times n n×n的方阵, n n n是点的个数。 g [ x ] [ y ] g[x][y] g[x][y]表示从 x x x y y y恰好经过 1 1 1条边的最短路长度。注意,这里的 g [ x ] [ x ] g[x][x] g[x][x]是不能赋值为 0 0 0的,因为它并不是真的邻接矩阵,它的对角线都要赋值为 + ∞ +\infty +。接下来考虑怎么递推。设 d [ k ] [ x ] [ y ] d[k][x][y] d[k][x][y]是从 x x x走到 y y y恰好经过 k k k条边的最短路长度,那么对于任意的正整数 a a a b b b,容易看出: d [ a + b ] [ x ] [ y ] = min ⁡ z { d [ a ] [ x ] [ z ] + d [ b ] [ z ] [ y ] } d[a+b][x][y]=\min_z\{ d[a][x][z]+d[b][z][y] \} d[a+b][x][y]=zmin{d[a][x][z]+d[b][z][y]}这里相当于在枚举从 x x x走恰好 a a a条边能到达的点是哪个。我们已经知道了 d [ 1 ] d[1] d[1],相当于要求 d [ N ] d[N] d[N]。这里的思路是和快速幂是一样的,先求 d [ N 2 ] d[\frac{N}{2}] d[2N],然后做一次与矩阵乘法非常相似的操作,就能得到 d [ N ] d[N] d[N]了。关于快速幂参考https://blog.csdn.net/qq_46105170/article/details/113823747。

也可以这样看。如果定义新的方阵乘法 C = A B C=AB C=AB为: C [ x ] [ y ] = min ⁡ z { A [ x ] [ z ] + B [ z ] [ y ] } C[x][y]=\min_z\{A[x][z]+B[z][y]\} C[x][y]=zmin{A[x][z]+B[z][y]}那么这个乘法在 { O , A , A 2 , . . . } \{O, A, A^2,...\} {O,A,A2,...}这个集合上是满足结合律的,其中 O O O是对角线全是 0 0 0,其余全是 + ∞ +\infty +的方阵。可以结合图论来看, A k A^k Ak实际上就是恰好走 k k k条边的最短路邻接矩阵。那么 ( A x A y ) A z = A x + y A z = A x + ( y + z ) = A x A y + z = A x ( A y A z ) (A^xA^y)A^z=A^{x+y}A^z=A^{x+(y+z)}=A^xA^{y+z}=A^x(A^yA^z) (AxAy)Az=Ax+yAz=Ax+(y+z)=AxAy+z=Ax(AyAz)。所以快速幂是可以做的。进一步也可以证明这个乘法在一般的矩阵上也是满足结合律的。考虑 A B C ABC ABC ( i , j ) (i,j) (i,j)元,容易知道 min ⁡ l { min ⁡ k { a i k + b k l } + c l j } = min ⁡ k { a i k + min ⁡ l { b k l + c l j } } \min_l\{\min_k\{a_{ik}+b_{kl}\}+c_{lj}\}=\min_k\{a_{ik}+\min_l\{b_{kl}+c_{lj}\}\} lmin{kmin{aik+bkl}+clj}=kmin{aik+lmin{bkl+clj}}所以结合律是成立的。有结合律的结构上是可以做快速幂的。

代码如下:

#include 
#include 
#include 
using namespace std;

const int N = 210;
int n, m, S, E, k;
int g[N][N];
int res[N][N], tmp[N][N];

// 对a和b做乘法,然后将结果存进c里
void mul(int c[][N], int a[][N], int b[][N]) {
    memset(tmp, 0x3f, sizeof tmp);
    for (int k = 1; k <= n; k++) 
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                tmp[i][j] = min(tmp[i][j], a[i][k] + b[k][j]);

    memcpy(c, tmp, sizeof tmp); 
}

// 用快速幂求一下g的k次幂
void qmi(int k) {
	// 先将res初始化为恰好走0条边的最短路长度,所以要把res[i][i]初始化为0
    memset(res, 0x3f, sizeof res);
    for (int i = 1; i <= n; i++) res[i][i] = 0;
    
    // 接下来是快速幂。g是恰好走1条边的最短路长度
    while (k) {
        if (k & 1) mul(res, res, g);
        mul(g, g, g);
        k >>= 1;
    }
}

int main() {
    cin >> k >> m >> S >> E;

    memset(g, 0x3f, sizeof g);

	// 做一下离散化
    unordered_map<int, int> ids;
    if (!ids.count(S)) ids[S] = ++n;
    if (!ids.count(E)) ids[E] = ++n;
    S = ids[S], E = ids[E];

    while (m--) {
        int a, b, c;
        cin >> c >> a >> b;
        if (!ids.count(a)) ids[a] = ++n;
        if (!ids.count(b)) ids[b] = ++n;
        a = ids[a], b = ids[b];

        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    // 做快速幂
    qmi(k);

    cout << res[S][E] << endl;

    return 0;
}

时间复杂度 O ( n 3 log ⁡ k ) O(n^3\log k) O(n3logk),空间 O ( n 2 ) O(n^2) O(n2) n n n是图的点数。

你可能感兴趣的:(ACLG,搜索,图论与网络,算法,图论)