AcWing 345 牛站 【BellmanFord算法,非正解】

AcWing 345 牛站

题目传送门

一、题目描述

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

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

注意: 数据保证一定有解。

输入格式
1 行:包含四个整数 NTSE

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

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

二、bellmanFord之恰好k条边

有边数限制的最短路径问题,第一反应自然是 BellmanFord算法,只需对BellmanFord算法的逻辑稍加修改,再吸吸氧应该就可以AC

BellmanFord算法的存图方式比较牛X(yxc大佬原话),用一个结构体Edge(u,v,w),然后开一个数组就行了,随便存,与之前学习的邻接表不同

原因:因为BellmanFord需要 遍历每一条边,怎么能快速遍历每一条边就是关键。
以前学习过的邻接表,是按点做索引的 ,记录这个点出发到达哪些点。
如果采用邻接表,就需要枚举每个点,然后二次循环找出每条边,就麻烦了,不如直接以边为索引来的快了。

问题
Q1:bellmonFord 算法解决的问题是 最多 经过k条边的最短路,而题目问的是 恰好经过 k条边的最短路径,怎么解决呢?

AbellmonFord 每次 强制转移 即可,即把新的距离数组赋值为正无穷。
这样每迭代一次数组中i代表的一定是恰好走k步的步数。

Q2:为什么要加上 备份数组
A:每次在处理dist数组前备份一下到backup数组,是为了每次迭代只使用上一次的结果,本次的迭代结果不用于本轮次的其它迭代,这样才能保证使用边数的限制有效,就不会发生串联了。

#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int M = 1000010;
int dist[N], backup[N];
struct Edge {
    int a, b, c;
} edges[M];
int n, k, m, s, t;

int bellmanFord() {
    //初始化
    memset(dist, 0x3f, sizeof(dist));
    dist[s] = 0;

    //经过k次迭代
    for (int i = 0; i < k; i++) {
        memcpy(backup, dist, sizeof(dist)); //使用备份,防止串联
        memset(dist, 0x3f, sizeof(dist));   //这句是精髓,恰好需要在循环内进行全新初始化
        for (int j = 0; j < m; j++) {
            int a = edges[j].a, b = edges[j].b, c = edges[j].c;
            dist[b] = min(dist[b], backup[a] + c);
            dist[a] = min(dist[a], backup[b] + c);
        }
    }
    return dist[t];
}
int main() {
    scanf("%d %d %d %d", &k, &m, &s, &t);

    for (int i = 0; i < m; i++) {
        int a, b, c;
        scanf("%d %d %d", &c, &a, &b);
        edges[i].a = a, edges[i].b = b, edges[i].c = c;
    }
    printf("%d", bellmanFord());
    return 0;
}

三、离散化

本题没有离散化的必要!有的博文说必须需要离散化,纯粹是扯淡,人云亦云而已。

原因:

  • bellmanFord是按边来枚举的,而且枚举的是Edge数组,不管你是不是离散化,边的数量都不会变化。
  • 不超过k 或者 恰好是k 条,都是说bellmanFord的外层循环是0k,你离不离散化都没有影响。
  • 非要说离散化有用的话,就是原来993这样的大号,被你搞成了3这样的小号,看着方便而已,其它毫无影响,该吸氧通过的,还得吸氧;该过不了的,还是过不了。
  • 当然,只是说本题离散化没用,不是说图论的离散化没用,可别理解错了。

附上两种离散化的代码,起到学习的作用吧:

离散化技巧一

#pragma GCC optimize(2) //累了就吸点氧吧~不吸氧,通过8/12个数据

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 205, M = 105;
//现在看来,这个代码写的好垃圾啊,离散化用的不好。代码长
//而且,在用旧的号码记录到邻接表中,然后再去修改,逻辑太长,垃圾!
//相对于另外两套代码,都是在使用hashTable或者unordered_map进行映射后
//再放入邻接表(邻接矩阵),就方便多了~
int m;    //图的边数
int k;    //恰好是k条边
int s, t; //起点和终点

struct Edge {
    int a; //起点
    int b; //终点
    int w; //边长
} edges[M];

int p[N]; //使用了哪些点,为了照顾后面的离散化,肯定要尽可能的把点都存储起来,
int tot;
int dist[N], backup[N];

//通过STL将原来的x找到现在新的下标位置
int get(int x) {
    int l = 0, r = tot;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (p[mid] >= x)
            r = mid;
        else
            l = mid + 1;
    }
    return l;
}

int bellmanFord() {
    //最多经过 k 条边:在循环外初始化一次
    //恰好经过 k 条边:除在循环外初始化一次外,还需要在第一层循环中memcpy(backup)后面再次初始化
    memset(dist, 0x3f, sizeof dist);
    dist[s] = 0; //出发点

    //恰好是k条边,迭代k次
    for (int i = 0; i < k; i++) {
        memcpy(backup, dist, sizeof dist);
        memset(dist, 0x3f, sizeof dist);
        for (int j = 1; j <= m; j++) { //枚举m条边
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], backup[a] + w);
            dist[a] = min(dist[a], backup[b] + w);
        }
    }
    //计算在n条限定下的最短距离
    return dist[t];
}

int main() {
    //加快读入
    scanf("%d %d %d %d", &k, &m, &s, &t);

    for (int i = 1; i <= m; i++) {
        int a, b, c;
        scanf("%d %d %d", &c, &a, &b); //一条边的边长以及构成边的两个点的编号
        edges[i] = {a, b, c};
        //记录有哪些点,准备离散化去重
        p[tot++] = a, p[tot++] = b;
    }

    //离散化
    sort(p, p + tot);
    tot = unique(p, p + tot) - p;

    //枚举每条边,将第i条边的起点和终点转化为离散化后的新序号
    for (int i = 1; i <= m; i++)
        edges[i].a = get(edges[i].a), edges[i].b = get(edges[i].b);

    //将起点和终点也转化为离散化后的新点号
    s = get(s), t = get(t);

    //调用bellmanFord算法
    printf("%d\n", bellmanFord());
    return 0;
}

离散化技巧二

#pragma GCC optimize(2) //累了就吸点氧吧~

#include <bits/stdc++.h>
using namespace std;
int id[1010];
const int N = 210;
const int M = 1000010;
int d[N], backup[N];
struct Edge {
    int a, b, c;
} edges[M];
int n, k, m, s, t;

int bellmanFord() {
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    for (int i = 0; i < k; i++) {
        memcpy(backup, d, sizeof(d));
        //与最多经过k条边这里不同!
        memset(d, 0x3f, sizeof(d));
        for (int j = 0; j < m; j++) {
            int a = edges[j].a, b = edges[j].b, c = edges[j].c;
            //与最多经过k条边这里不同!
            d[b] = min(d[b], backup[a] + c);
            d[a] = min(d[a], backup[b] + c);
        }
    }
    return d[t];
}
int main() {
    //加快读入
    scanf("%d %d %d %d", &k, &m, &s, &t);

    //起点分配1号
    if (!id[s]) id[s] = ++n;

    //结束结点为2号,为了防止起点和终点是一个,采用了判断的办法
    if (!id[t]) id[t] = ++n;

    //修改原来s和t的离散化后的值
    s = id[s], t = id[t];

    // m条边
    for (int i = 0; i < m; i++) {
        int a, b, c;
        scanf("%d %d %d", &c, &a, &b);
        if (!id[a]) id[a] = ++n;
        if (!id[b]) id[b] = ++n;
        edges[i].a = id[a], edges[i].b = id[b], edges[i].c = c;
    }
    //上面的离散化简直太牛B了!
    printf("%d", bellmanFord());
    return 0;
}
posted @   糖豆爸爸  阅读(96)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Live2D
点击右上角即可分享
微信分享提示