最短路模板题,如果对最短路不是很熟悉的同学请移步:
传送门

进入正题

此题与其他题不同的是,每新增条边,就必须存储,然后等1操作到达时跑最短路,于是我们有dijkstra和SPFA两种跑最短路的方法:

其次,如何处理无法到达呢。只需要if(dis[]==inf)就行了。

因为我们dis一开始初始化为inf,如果在接下来的跑最短路中没有被更新(也就是dis==inf),说明由当前起点是到不了这个点的。

dinjkstra:

dijkstra的核心就在于它的算法步骤:

算法步骤:
1. 找离起点x最近的未讨论过的点k。
2. 判断经过k点,起点x到其他点的距离是否缩短,如缩短
则更新。将k点标记为已讨论。
3. 重复进行第1步,直到所有点都被讨论过为止。

所以dijkstra的实质是贪心,但是在上述步骤1中,我们可以使用强大的stl中的优先队列来快速查找。

所以dijkstra的时间复杂度为

:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10005;
const ll inf=1e15;
struct node{    重载运算符,将dis关键字从小到大排序
    int num;
    ll dis;
    bool operator < (const node &a) const{
        return dis>a.dis;
    }
};
int n,m;
ll dis[N];
bool mark[N];
int Last[N],End[N],len[N],Next[N],tot;
void cb(int x,int y,int z){
    End[++tot]=y;
    len[tot]=z;
    Next[tot]=Last[x];
    Last[x]=tot;
}
void dijkstra(int s){
    for(int i=1;i<=n;i++){
        dis[i]=inf;
        mark[i]=false;
    }
    dis[s]=0;
    //mark[s]=true;
    priority_queue<node> q;       以dis作为关键字的小根堆
    q.push((node){s,0});
    while(q.size()){
        int u=q.top().num;     取出距离起点最近的点
        q.pop();
        if(mark[u])
            continue;
        mark[u]=true;
        for(int i=Last[u];i;i=Next[i]){   链式前向星
            int v=End[i];
            if(!mark[v] && dis[v]>dis[u]+len[i]){  更新最短距离
                dis[v]=dis[u]+len[i];
                q.push((node){v,dis[v]});
            }
        }
    }
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int opt,x,y,z;
        scanf("%d",&opt);
        if(opt){   opt==1 记录边
            scanf("%d %d %d",&x,&y,&z);
            cb(x,y,z);
            cb(y,x,z);
        }
        else{
            scanf("%d %d",&x,&y);
            dijkstra(x);     跑最短路
            if(dis[y]!=inf)   判断是否可以到达
                printf("%lld\n",dis[y]);
            else
                puts("-1");
        }
    }
    return 0;
}

SPFA(bellman-ford)

算法流程:

用一个队列来进行维护。初始时将起点加入队列。

每次从队列中取出一个元素,并对他所连接的点进行松弛,若某个点松弛成功(则通过那个点到起点距离缩短),则将其入队。直到队列为空

简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点

SPFA的时间复杂度是O(kE),k一般取2左右(k是增长很快的 函数ackermann的反函数,2^65536次方也就5以下 ),可以处理负边。

:(其实跟dijkstra差不多)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10005;
const ll inf=1e15;
int n,m;
ll dis[N];
bool mark[N];
int Last[N],End[N],len[N],Next[N],tot;
void cb(int x,int y,int z){
    End[++tot]=y;
    len[tot]=z;
    Next[tot]=Last[x];
    Last[x]=tot;
}
void spfa(int s){
    for(int i=1;i<=n;i++){
        dis[i]=inf;
        mark[i]=false;
    }
    dis[s]=0;
    mark[s]=true;
    queue<int> q;
    q.push(s);
    while(q.size()){
        int u=q.front();
        q.pop();
        mark[u]=false;
        for(int i=Last[u];i;i=Next[i]){
            int v=End[i];
            if(dis[v]>dis[u]+len[i]){
                dis[v]=dis[u]+len[i];
                if(!mark[v]){
                    q.push(v);
                    mark[v]=true;
                }
            }
        }
    }
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int opt,x,y,z;
        scanf("%d",&opt);
        if(opt){
            scanf("%d %d %d",&x,&y,&z);
            cb(x,y,z);
            cb(y,x,z);
        }
        else{
            scanf("%d %d",&x,&y);
            spfa(x);
            if(dis[y]!=inf)
                printf("%lld\n",dis[y]);
            else
                puts("-1");
        }
    }
    return 0;
}

SPFA判断负环可以看文章上方传送门

其实这道题还能用floyd

是不是很惊讶,floyd的时间复杂度怎么可能过的了!!?

但是我们可以边记录边进行更新,这样这道题的时间复杂度就是了!!!

题解区里有大佬讲的很清楚了,这里我就不细讲了。。。

总结:

这道题其实还是一道很不错的题,可以为巩固最短路来用!