P4779 【模板】单源最短路径(标准版)题解

原题链接 https://www.luogu.org/problemnew/show/P4779

若还未食用弱化版的同学请先做这个qwq https://www.luogu.org/problemnew/show/P3371

刚刚做完了弱化版的我,看到了这个标准版 双倍经验美滋滋qwq

把弱化版的SPFA模板打上去,改了下数据范围,提交!悲催的TLE了四个点!

很显然,这个题的数据是卡SPFA的,使得时间复杂度是SPFA最坏的复杂度!

那咋办呢?我们看到了题目中已经说明了没有负权边,那么我们就可以用时间复杂度更为稳定的Dijkstra算法!

介绍一下下算法原理及实现:

假设我们有白点和蓝点,蓝点表示还未被松弛的点,白点表示已经松弛过的点,vis[i]=1代表是白点已经被松弛过了,vis[i]=0代表是蓝点还未被松弛过。我们的目标就是将所有的蓝点转化成白点;

1.从起点s开始,松弛它所有的出边,若某条边松弛成功且这条边的终点不在队列里,就让它入队,将起点标记为白点:vis[s]=1;

2.找到一个距离起点s最小的点(用优先队列实现),若该点是蓝点,那么松弛它所有的出边,若某条边松弛成功且这条边的终点不在队列里,就让它入队,将起点标记为白点:vis[i]=1;

重复第2个步骤,直到队列为空。

Dijkstra为什么是正确的?

当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新.所以在第22步中找出的蓝点xx必然满足:dis[x]:dis[x]已经是起点到xx的最短路径..我们不断选择全局最小值进行标记和拓展,最终可以得到起点到每个节点的最短路径的长度。

算法图解

1.选定A节点并初始化,如上述步骤3所示

2.执行上述 4、5两步骤,找出U集合中路径最短的节点D 加入S集合,并根据条件 if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 来更新U集合

3.这时候 A->B, A->C 都为3,没关系。其实这时候他俩都是最短距离,如果从算法逻辑来讲的话,会先取到B点。而这个时候 if 条件变成了 if ( 'B 到 C,E 的距离' + 'AB 距离' < 'A 到 C,E 的距离' )如图所示这时候A->B距离 其实为 A->D->B

 

4.思路就是这样,往后就是大同小异了

链接:https://www.jianshu.com/p/ff6db00ad866
说了这么多,下面上代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int edge_sum;                              //暂时存边数 
int n,m,s;
const int inf=1e9;
int read()
{
    int a=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        a=a*10+(ch-'0');
        ch=getchar();
    }
    return a;
}
int head[200001],vis[100001],dis[100001];  //head[i]表示以i结点为始点的最后一条出边,vis[i]表示i结点是否为白点,dis[i]表示i结点到起点s的最短距离 
struct edge                                //定义一个结构体来存边的信息 
{
    int dis,pos,from,to,next;               
    bool operator < (const edge &x)const   //重载小于号,根据dis来从小到大排序 
    {
        return x.dis<dis;
    }
}a[200001];
priority_queue<edge> q;                    //优先队列 
void add(int from,int to,int dis)          //链式前向星存图 
{
    edge_sum++;
    a[edge_sum].next=head[from];
    a[edge_sum].dis=dis;
    a[edge_sum].from=from;
    a[edge_sum].to=to;
    head[from]=edge_sum;
}
int main()
{
    n=read();m=read();s=read();
    for(int i=1;i<=m;i++)
    {
        int u=read();
        int v=read();
        int w=read();
        add(u,v,w);                        //建图 
    }
    for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;//初始化 
    dis[s]=0;
    q.push((edge){0,s});                   //敲黑板,难点重点!!!我们优先队列定义的是edge类型的,所以我们放入一个edge类型的集合,集合中的第一个元素对应着结构体中的第一个成员,后之同理 
                                           //换句话说,这步操作就是将一个距离起点为0,编号为s的结点入队 
    while(!q.empty())                    
    { 
        edge front=q.top();                //记录队列里的第一个元素,也就是dis最小的元素 
        q.pop();                           //弹出队首元素 
        int wz=front.pos;                  //wz是该边的始点编号 
        if(vis[wz]==1) continue;           //如果该点已经在优先队列里了,那就没有松弛的必要了 
        vis[wz]=1;                         //将始点标记为白点     
        for(int i=head[wz];i;i=a[i].next)  //访问所有的出边 
        {
            int zd=a[i].to;                //这条边的终点 
            if(dis[zd]>dis[wz]+a[i].dis)
            {
                dis[zd]=dis[wz]+a[i].dis;  //松弛 
                if(vis[zd]==0)             //若终点不在队列里的话 
                { 
                    q.push((edge){dis[zd],zd}); //将松弛过的点都入队 
                }
            }
        } 
    }
    for(int i=1;i<=n;i++)
    printf("%d ",dis[i]);
    return 0;
}

完结撒花qwq 双倍经验喽

原文地址:https://www.cnblogs.com/xcg123/p/10927597.html