• AcWing 345 牛站


    \(AcWing\) \(345\) 牛站

    题目传送门

    \(bellmanford\)解法 非正解

    一、题目描述

    给定一张由 \(T\) 条边构成的无向图,点的编号为 \(1\)\(1000\) 之间的整数。

    求从起点 \(S\) 到终点 \(E\) 恰好 经过 \(N\) 条边(可以重复经过)的 最短路

    注意: 数据保证一定有解

    输入格式
    \(1\) 行:包含四个整数 \(N\)\(T\)\(S\)\(E\)

    \(2..T+1\) 行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。

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

    二、前导知识

    三、题目解析

    本题并不是让我们求路径的条数,而是求 在路径条数限定的情况下,求最短路径长度

    \(g\)是图的邻接矩阵,\(t[i][j]\)表示从\(i\)恰好经过两条边到达\(j\)最短路径长度 ,则

    \[\large t[i][j] = min(g[i][k] + g[k][j]) \]

    可以想象一下,\(i\),\(j\)两个点,如果之间有两条边,则需要引入一个点才行,设为\(k\),则路径长度就是\(g[i][k]+g[k][j]\),而\(k\)的范围是$ \in [1,n]$,代码:

    for(int k = 1;k <= n;k++)
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= n;j++)
                t[i][j] = min(t[i][j],g[i][k] + g[k][j]);
    

    这被称作:广义矩阵乘法

    快速幂对广义矩阵乘法的优化

    广义矩阵乘法可以求\(i\)经过 两条边 到达\(j\)最短路径 长度,则对\(g\)\(k\)自乘 就可以求经过\(k\)条边的 最短路径 长度了。

    void mul(int a[][N],int b[][N]){
        int t[N][N];
        memset(t,0x3f,sizeof t); //预求最小,先设最大
        for(int k = 1;k <= n;k++)
            for(int i = 1;i <= n;i++)
                for(int j = 1;j <= n;j++)
                    t[i][j] = min(t[i][j],a[i][k] + b[k][j]);
        
        // sizeof 不能在此处使用,因为a数组其实是指针传入,无法获取大小
        // 有两个办法可以解决: 
        // (1)采用全局的sizeof t进行替代 (简单)
        // (2)在函数中增加大小这样一个参数,由调用者赋值传入 (麻烦)
        memcpy(a,t,sizeof t);
    }
    void qmi(){
        memcpy(f,a,sizeof f);
        k--;
        while(k){
            if(k & 1)   mul(f,g);
            mul(g,g);
            k >>= 1;
        }
    }
    

    离散化

    值得注意的是点的编号最大是1000,最多只有100条边,也就是最多200个节点,所以可以对节点编号做下 离散化,较为简单,参考代码即可。

    小坑一枚

    还有个要注意的地方时,本题读取边的信息的时候是先读取边长再读取两个节点的编号,一般题目都是最后读取边权,要注意不要写错数据的读取代码。

    不作离散化会怎么样?

    通过了 \(5/12\)个数据 结果: \(Time\) \(Limit\) \(Exceeded\), 无法\(AC\)

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1010;
    int s, e;
    int n, m;
    int k;
    int g[N][N], f[N][N];
    int t[N][N];
    
    void mul(int a[][N], int b[][N]) {
        memset(t, 0x3f, sizeof t);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                for (int k = 1; k <= n; k++)
                    t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
        memcpy(a, t, sizeof t);
    }
    void qmi() {
        memcpy(f, g, sizeof f);
        k--;
    
        while (k) {
            if (k & 1) mul(f, g);
            mul(g, g);
            k >>= 1;
        }
    }
    int main() {
        scanf("%d %d %d %d", &k, &m, &s, &e);
    
        memset(g, 0x3f, sizeof g); //地图初始化
        while (m--) {
            int a, b, c;
            scanf("%d %d %d", &c, &a, &b);
            n = max({n, a, b});
            g[a][b] = g[b][a] = min(g[a][b], c); //无向图
        }
    
        //矩阵快速幂
        qmi();
    
        //输出
        printf("%d\n", f[s][e]);
        return 0;
    }
    

    使用\(id\)数组做离散化

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 205;
    int id[1010];
    int s, e;
    int n, m;
    int k;
    int g[N][N], f[N][N];
    int t[N][N];
    
    void mul(int a[][N], int b[][N]) {
        memset(t, 0x3f, sizeof t);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                for (int k = 1; k <= n; k++)
                    t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
        memcpy(a, t, sizeof t);
    }
    void qmi() {
        // 写法1:使用一个空的矩阵,需要执行k次幂
        // memset(f, 0x3f, sizeof f);
        // for (int i = 1; i <= n; i++) f[i][i] = 0;
    
        //写法2,使用一个拷贝矩阵,需要执行k-1次幂
        memcpy(f, g, sizeof f);
        k--;
    
        while (k) {
            if (k & 1) mul(f, g);
            mul(g, g);
            k >>= 1;
        }
    }
    int main() {
        scanf("%d %d %d %d", &k, &m, &s, &e);
    
        //离散化
        id[s] = ++n;          //起点编号
        id[e] = ++n;          //终点编号
        s = id[s], e = id[e]; //新起点,新征程
    
        memset(g, 0x3f, sizeof g); //地图初始化
        while (m--) {
            int a, b, c;
            scanf("%d %d %d", &c, &a, &b);
            if (!id[a]) id[a] = ++n; // a节点离散化
            if (!id[b]) id[b] = ++n; // b节点离散化
            a = id[a], b = id[b];
            g[a][b] = g[b][a] = min(g[a][b], c); //无向图
        }
    
        //矩阵快速幂
        qmi();
    
        //输出
        printf("%d\n", f[s][e]);
        return 0;
    }
    

    使用\(STL+unordered\_map\)进行离散化

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 205;
    unordered_map<int, int> id;
    int s, e;    //起点 终点
    int n, m;    //离散化后的节点号  m条边
    int k;       //恰好k条边
    int g[N][N]; //图
    int f[N][N]; //最短距离
    
    //矩阵乘法
    void mul(int a[][N], int b[][N]) {
        int t[N][N];
        memset(t, 0x3f, sizeof t);
        for (int k = 1; k <= n; k++)
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++)
                    //求指定边数情况下的最短距离
                    //广义上的矩阵乘法
                    t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
        memcpy(a, t, sizeof t);
    }
    //快速幂
    void qmi() {
        // 写法1:使用一个空的矩阵,需要执行k次幂
        // memset(f, 0x3f, sizeof f);
        // for (int i = 1; i <= n; i++) f[i][i] = 0;
    
        //写法2,使用一个拷贝矩阵,需要执行k-1次幂
        memcpy(f, g, sizeof f);
        k--;
    
        while (k) {
            if (k & 1) mul(f, g); //矩阵快速幂
            mul(g, g);
            k >>= 1;
        }
    }
    // AC 481 ms
    // 这个速度真是很牛X
    int main() {
        scanf("%d %d %d %d", &k, &m, &s, &e);
        //起点的离散化后号为1
        id[s] = ++n;
        //如果e与s不同,那么e的新号为2,否则为1
        if (!id.count(e)) id[e] = ++n;
        //重新对s和e给定新的号码
        s = id[s], e = id[e];
        //初始化邻接矩阵
        memset(g, 0x3f, sizeof g);
        while (m--) {
            int a, b, c;
            scanf("%d %d %d", &c, &a, &b);
            if (!id.count(a)) id[a] = ++n; //记录点的映射关系a-> id[a]
            if (!id.count(b)) id[b] = ++n; //记录点的映射关系b-> id[b]
            a = id[a], b = id[b];          //对a,b给定新的号码
            //利用新的号码将边长c记录到邻接矩阵中
            g[a][b] = g[b][a] = min(g[a][b], c);
        }
        //快速幂+动态规划思想
        qmi();
        //输出从起点到终点,恰好经过k条边的最短路径
        printf("%d\n", f[s][e]);
        return 0;
    }
    
  • 相关阅读:
    springboot maven打包插件
    maven打包指定main入口插件
    团队开发环境一致性性要求
    springboot 在idea中实现热部署
    IDEA 2018.1可用License服务(持续更新)
    IDEA打jar包
    3月18号
    3月17号
    3月16号
    3月13号
  • 原文地址:https://www.cnblogs.com/littlehb/p/16043039.html
Copyright © 2020-2023  润新知