最短路径(上机试题)

题目链接

题目描述:N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离

解题思路(方法一)Dijkstra算法 + string形式存储路径长度

这个题目我花了很久的时间才通过牛客网的在线测试,在此记录我踩一下过的坑

  1. 首先题目说的是2的K次幂,我一开始给理解成了2的K倍
  2. 刚开始我是用int类型保存路径长度的,题目说“数值太大的以MOD 100000 的结果输出”,我就在求解过程中不断求余,把求余的结果保存为路径长度。后来测试一直没通过,我隔了一天才反应过来,保存余数是错误的,例如:本来路径长度是100008和9999,求余后,变成8和9999,8 < 9999跟100008 > 9999是完全对应不上的。意识到这个错误后,我就开始用string类型保存路径长度,增加了4个有关操作string的函数。
  3. 经过以上的纠错,我提交代码,发现还是没通过测试,我对比了自己代码的输出和标准输出,发现让人迷惑的是,有些行的结果是正确的,有些却不是。我第一反应是去检查我手写的那4个有关操作string的函数,检查来检查去,发现没有什么很大的bug,于是我一时间无从下手。又过了一天,我尝试对结构体Point的小于号重载函数进行修改,发现程序的结果有了很大改变,虽然仍然没有通过,但是我隐约意识到,程序的bug与这个地方有关。后来果真,是jud函数里compare方法用得不对,直接用<、>、=便能达到程序需要的判断效果

代码

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 101;
const int MOD = 100000;
// 比较两个数字字符串的大小,s1<s2: -1,s1==s2: 0,s1>s2: 1
int jud(string s1,string s2) {
    int len1 = s1.size();
    int len2 = s2.size();
    if(len1==len2)
        if(s1<s2) return -1;
        else if(s1==s2) return 0;
        else return 1;
    else if(len1<len2)
        return -1;
    else
        return 1;
}
/*
int jud(string s1,string s2) {
    int len1 = s1.size();
    int len2 = s2.size();
    if(len1==len2)
        return s1.compare(s2);
    else if(len1<len2)
        return -1;
    else
        return 1;
}*/
// 两个数字字符串 求和操作
string Add(string s1,string s2) {
    if(s1.size() < s2.size())
        swap(s1,s2);  // 预设定:s1长度较长
    string res = "";
    int len1 = s1.size(),len2 = s2.size(),carry = 0;
    for(int i=len1-1,j=len2-1; i>=0; i--,j--) {
        if(j>=0)
            carry += (s1[i]-'0') + (s2[j]-'0');
        else
            carry += (s1[i]-'0');
        char temp = (carry%10 + '0');
        res = temp + res;
        carry /= 10;
    }
    while(carry>0) {
        char temp = (carry%10 + '0');
        res = temp + res;
        carry /= 10;
    }
    return res;
}
// 数字字符串 乘法操作(乘以一个 个位数字num)
string Multiple(string s1,int num) {
    string res = "";
    int len1 = s1.size(),carry = 0;
    for(int i=len1-1; i>=0; i--) {
        carry += (s1[i]-'0')*num;
        char temp = (carry%10 + '0');
        res = temp + res;
        carry /= 10;
    }
    while(carry>0) {
        char temp = (carry%10 + '0');
        res = temp + res;
        carry /= 10;
    }
    int pos;
    for(pos=0;pos<res.size()-1;pos++){
        if(res[pos]!='0') return res.substr(pos);
    }
    return res.substr(pos);
}
// 将string类型转换为int类型,数值太大就 MOD 100000
int Tran(string s1){
    int remainder = 0;
    int len = s1.size();
    for(int i=0;i<len;i++){
        remainder*=10;
        int temp = s1[i] - '0';
        remainder += temp;
        remainder %= MOD;
    }
    if(remainder==0){
        if(len==0|| len==1) return 0;
        else return MOD;
    }else return remainder;
}
struct Edge {
    int to;
    string length;
    Edge(int t,string l):to(t),length(l) {}
};
struct Point {
    int number;
    string distance;
    Point(int n,string d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        if(jud(distance,y.distance)==-1)
            return false;
        return true;
    }
};
vector<Edge> graph[MAXN];
string dis[MAXN];
void Dijkstra(int s){
    dis[s] = "0";
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(myQueue.empty()==false){
        int u =myQueue.top().number;
        myQueue.pop();
        for(int i=0;i<graph[u].size();i++){
            int p = graph[u][i].to;
            string dd = graph[u][i].length;
            if(dis[p]=="INF" || jud(dis[p],Add(dis[u],dd)) == 1){
                dis[p] = Add(dis[u],dd);
                myQueue.push(Point(p,dis[p]));
            }
        }
    }
}
int main() {
    int N,M;
    while(cin>>N>>M){
        memset(graph,0,sizeof(graph));
        fill(dis,dis+N,"INF");
        string K = "1";
        int p1,p2;
        for(int i=1;i<=M;i++){
            cin>>p1>>p2;
            graph[p1].push_back(Edge(p2,K));
            graph[p2].push_back(Edge(p1,K));
            K = Multiple(K,2);
        }
        Dijkstra(0);
        for(int i=1;i<N;i++){
            if(dis[i]=="INF") cout<<"-1"<<endl;
            else cout<<Tran(dis[i])<<endl;
        }
    }
    return 0;
}

解题思路(方法二):这是一道披着最短路径外衣的最小生成树

第k条路径的长度 > 前k-1条路径的总和

  • 在添加第k条路径时,如果路径k连接的两个城市A 、B已经通过其他路径间接的连通了,那么第k条路径绝对不是AB间的最短路径(第k条路径之后的路径也不可能有比当前AB路径更短的路径了,因为第k条路径的长度会大于前k-1条路径的总和)
  • 在添加第k条路径时,如果路径k连接的两个城市A 、B是第一次被连通了(也就是说此前没有任何路径能连通AB,包括通过多条路径间接连通),那么路径k就是AB之间的最短距离了,因为以后不可能存在更短的路径连接AB,以后的单条路径只会越来越长。
  1. 构造一个并查集,每个城市指向自己,二维数组dis记录城市之间的距离,初始化为INF不可达,dis[i][i]初始化为0
  2. 取第一条路径,第一条路径连接了城市p1-1、p2-1,城市p1-1、p2-1间最短路径就是第一条路径,然后把p1-1、p2-1合并在一个集合里
  3. 再取一条路径
  4. 如果这条边连接的城市p1-2、p2-2,已经在同一个集合里了(即已经联通了),那么当前的路径一定不是p1-2、p2-2之间的最短路径了,忽略之,继续看下一条路径。
  5. 如果这条路径连接的城市p1-2、p2-2不在同一个集合里(之前没有联通过),很好,这条路径是p1-2、p2-2之间的最短路径;除此之外我们还可以看看通过这条路径能不能让p1-2集合里的城市到p2-2集合里的城市之间的路径更短呢?即是不是dis[s][t]>(d[s][p1-2]+dist+d[p2-2][t]) ?(注意s和p1-2一个集合,p1-2和t一个集合),如果是的话可以更新一下这个值。
  6. 重复(3)直到所有路径都处理过了
  7. 0号城市到各个城市i的最小路径结果就是dis[0][i]了
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 101;
const int MOD = 100000;
const int INF = INT_MAX;
int dis[MAXN][MAXN];   // 无向图
int father[MAXN];
int height[MAXN];
void Initial(int n) {
    memset(dis,INF,sizeof(dis));
    for(int i=0; i<n; i++) {
        father[i] = i;
        height[i] = 0;
        for(int j=0; j<n; j++) {
            if(i==j)
                dis[i][j]=0;
            else
                dis[i][j]=INF;
        }
    }
}
int Find(int x) {
    if(father[x]!=x)
        father[x] = Find(father[x]);
    return father[x];
}
void Union(int x,int y) {
    int root1 = Find(x);
    int root2 = Find(y);
    if(root1!=root2) {
        if(height[root1]<height[root2])
            father[root1] = root2;
        else if(height[root1]>height[root2])
            father[root2] = root1;
        else {
            father[root2] = root1;
            height[root1]++;
        }
    }
}
int main() {
    int N,M;
    while(cin>>N>>M) {
        Initial(N);
        int K = 1;
        int p1,p2;
        for(int i=1; i<=M; i++) {
            cin>>p1>>p2;
            /*if(Find(p1)!=Find(p2)) {
                Union(p1,p2);
                dis[p1][p2] = K;
                dis[p2][p1] = K;
                for(int s=0; s<N; s++) {
                    for(int t=0; t<N; t++) {
                        if(dis[s][t]==INF && dis[s][p1]!=INF && dis[p2][t]!=INF) {
                            dis[s][t] = (dis[s][p1] + K + dis[p2][t])%MOD;
                        }
                    }
                }
            }*/
            if(Find(p1)!=Find(p2)){   /// 这个if下的代码段,比较关键,需要通过Find方法来判断dis的元素是否需要更新而不是通过INF来判断
                for(int s=0;s<N;s++){
                    if(Find(s)==Find(p1)){
                        for(int t=0;t<N;t++){
                            if(Find(t)==Find(p2)){
                                dis[s][t]=dis[t][s]=(dis[s][p1]+dis[t][p2]+K)%MOD;
                            }
                        }
                    }
                }
                Union(p1,p2);  //  这个要放在最后,不然前面循环中的判断将永远不会执行
            }
            K = (K*2)%MOD;
        }
        for(int i=1; i<N; i++) {  // 输出结果
            if(dis[0][i]==INF)
                cout<<"-1"<<endl;
            else
                cout<<dis[0][i]<<endl;
        }
    }
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值