原题链接 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.思路就是这样,往后就是大同小异了
#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 双倍经验喽