题解 单源最短路径 (SPFA)

huangzirui

2018-07-01 20:30:10

Solution

# **P3371** ---- [题目链接](https://www.luogu.org/problemnew/show/P3371) (因为我太弱了,所以大佬还是不要吐槽了 : ),谢谢)--大佬席------> ---- # 算法 ---- 我们用一种非常腻害的算法——$SPFA$ # 先来一组样例: ![](https://cdn.luogu.com.cn/upload/pic/21990.png) # (起点为1) 用一个队列维护搜索的依次顺序,再用一个数组维护**目前从1号点到i号点的最短长度(权值)**,然后命名为vis。当目前不可以到达时,先调整为INF。当输出时判断:当最短距离==INF,输出2147483647。 对了,要记得 ```cpp vis[1]=0; ``` 然后可以开始搜了! (目前在队列中的点: $1$) ---- ## 开始搜索点1: **因为从1号点可以到达点2,且从点1到达点2的距离 $1+0$ 小于原本的距离 $INF$。所以我们更新vis[2],并把点2加入队列。** 同理,调整vis[5],把点5加入队列。 ![](https://cdn.luogu.com.cn/upload/pic/21989.png) (目前在队列中的点: $2$,$5$) ---- ## 开始搜索点2: **因为从2号点可以到达点3,且从点1到达点3的距离 $4+1$ 小于原本的距离 $INF$。所以我们更新vis[3],并把点3加入队列。** 同理,调整vis[4],把点4加入队列。 ![](https://cdn.luogu.com.cn/upload/pic/21992.png ) (目前在队列中的点: $5$,$3$,$4$) ---- ## 开始搜索点5: **因为从5号点可以到达点3,且从点1到达点3的距离 $1+2$ 小于原本的距离 $4$。所以我们更新vis[3],又因为点3已经加入了队列,所以不用重复加入。** ![](https://cdn.luogu.com.cn/upload/pic/21993.png ) (目前在队列中的点: $3$,$4$) ---- ## 开始搜索点3: **因为从3号点~~可以到达幻想乡~~什么也不能到达,所以什么也没有发生QWQ。** ![](https://cdn.luogu.com.cn/upload/pic/21993.png ) (目前在队列中的点: $4$) ---- ## 开始搜索点4: **虽然从4号点可以到达点5,但从点4到达点3的距离 $1+4$ 大于原本的距离 $2$。所以我们不用更新vis[3]。** ![](https://cdn.luogu.com.cn/upload/pic/21993.png ) (目前在队列中的点:无) ---- 于是输出: ``` 0 1 3 4 2 ``` ## 算法部分完毕! ---- # 建图 ---- 观察数据规模: **对于100%的数据:N<=10000,M<=500000** QWQ...... 考虑使用邻接矩阵... ```cpp int a[10010][10010]; ``` ~~点一下有惊喜~~ [![](https://cdn.luogu.com.cn/upload/pic/21996.png )](https://www.luogu.org/recordnew/show/8094663) QWQ...... 好吧,不说了,加入正题: # 新的建图方法——链式前向星!!! ~~已经会了的dalao们就跳过吧QWQ~~ ---- 窝不想讲吗~~~(捂脸)人家不想讲吗 [戳一下](http://baidu.apphb.com/?q=%E9%93%BE%E5%BC%8F%E5%89%8D%E5%90%91%E6%98%9F) (窝还是提供一份链式前向星的代码吧~~~) ```cpp int head[maxn];//从i点出发的第一条边 struct egde{ int to,next_,w;//分别代表每条线目的地,下一条线的下标和权值 }a[maxm] //这样就可以用下列代码来依次把从now点出发的边遍历 for(i=head[now];i;i=a[i].next_) //是不是很神奇啊! //后面的是加入一条边的 void add(int u,int y,int o){ //建图 len++; c[len].next_=head[u]; c[len].to=y; c[len].dis=o; head[u]=len; } ``` ---- ## 我再放上代码吧! ```cpp #include<bits/stdc++.h> #define INF 0x3f3f3f3f using namespace std; int i,j,k,n,m,s,len; int vis[10010]; int dis[10010];//判断是否进队 int head[500010]; int dl[500010],l,r; struct stu{ int next_,to,w; }c[500010]; void add(int u,int y,int o){ //建图 len++; c[len].next_=head[u]; c[len].to=y; c[len].w=o; head[u]=len; } void SPFA(){ //初始化 for(i=1;i<=n;i++){ vis[i]=INF; } vis[s]=0; l=0;r=1;dl[r]=s;dis[s]=1; //设置队列 while(l^r/*没用的位运算,也就是j!=r*/){ l++; int u=dl[l]; dis[u]=0; for(i=head[u];i;i=c[i].next_){//遍历每条从i点出发的边 int v=c[i].to; if(vis[v]>vis[u]+c[i].w){//如果路径长度小于原长度 vis[v]=vis[u]+c[i].w;//调整 if(!dis[v]){ //入队 dis[v]=1; r++; dl[r]=v; } } } } } int main(){ cin>>n>>m>>s; //输入 for(i=1;i<=m;i++){ int u,y,o; scanf("%d%d%d",&u,&y,&o); add(u,y,o); } SPFA(); //输出 for(i=1;i<=n;i++) if(vis[i]==INF)cout<<2147483647<<" "; else cout<<vis[i]<<" "; return 0; } ```