• 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 双倍经验喽

  • 相关阅读:
    go 学习笔记---chan
    golang学习笔记---Goroutine
    golang ---Learn Concurrency
    golang ----并发 && 并行
    golang 学习笔记 使用cmd
    并发程序与并行程序
    golang学习笔记 ---interface
    golang --- map如何判断key是否存在
    golang学习 ---defer语句
    golang --for语句
  • 原文地址:https://www.cnblogs.com/xcg123/p/10927597.html
Copyright © 2020-2023  润新知