最短路(模板)【CodeChef CLIQUED,洛谷P3371】

自TG滚粗后咕咕咕了这么久,最近重新开始学OI,也会慢慢开始更博了。。。。

最短路算法经典的就是SPFA(Bellman-Ford),Dijkstra,Floyd;

本期先讲两个经典的单源最短路算法:

首先是我最喜(hao)欢(xie)的SPFA(可惜经常被卡)

SPFA:

Warning:SPFA在OI竞赛中慎用,极易容易被卡!!!

基本流程:

从起点开始,每次将扫到的点入队,每个点遍历所有与其相连的点,并更新最短路,如果该点未入队,则将其入队;

均摊复杂度为$ O(KE) $(K=2),但因为SPFA在网格图中入队次数过多,导致卡成原始的Bellman-Ford($ o(VE) $),所以竞赛中不常用到(但还是要学会的);

丑陋的代码(洛谷P3371):

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 int n,m,x,y,z,tot,f[500001],q[1000001],h,t,s,first[500001],nxt[500001],last[500001],en[500001],len[500001];
 5 //f为记录最短路径的数组,q为队列;
 6 //first,nxt,last,en,len为平平无奇的邻接表; 
 7 bool b[10001];
 8 //判断是否在队列中 
 9 void add(int x,int y,int z)
10 {
11     ++tot;
12     if(first[x]==0) first[x]=tot; else nxt[last[x]]=tot;
13     en[tot]=y;
14     len[tot]=z;
15     last[x]=tot;
16 }
17 void SPFA(int x)
18 {
19     int k=first[x];
20     do
21     {
22         if((long long)len[k]+f[x]<f[en[k]])
23         {
24             f[en[k]]=len[k]+f[x];
25             if(not b[en[k]]) 
26             {
27                 ++t;
28                 q[t]=en[k];
29                 b[en[k]]=true;
30             }
31         }
32         k=nxt[k];
33     }
34     while(k!=0);
35 }
36 int main()
37 {
38     scanf("%d%d%d",&n,&m,&s);
39     for(int i=1;i<=m;++i)
40     {
41         scanf("%d%d%d",&x,&y,&z);
42         add(x,y,z);
43     }
44     for(int i=1;i<=n;++i)
45     f[i]=2147483647;
46     f[s]=0;
47     h=0;
48     t=0;
49     SPFA(s);
50     while(h<=t)
51     {
52         SPFA(q[h]);
53         b[q[h]]=false;
54         ++h;
55     }
56     for(int i=1;i<=n;++i)
57         printf("%d ",f[i]);
58     return 0;
59 }

Dijkstra:

最常用的最短路算法(蒟蒻的我最近才学会太菜了);

基本流程:

定义两个点的集合,S为已求出最短路的点集,T为未求出最短路的点集;

每次在T集合中找到一个Dis值最小的点,将其放入S集合中,并用它更新其他点的最短路;

由此,朴素的Dijkstra在每次选择操作中暴力枚举每个点复杂度为$ O(V) $,更新时的复杂度也为$ O(V) $,及时间复杂度为$ O(V^2) $;

聪(AK)明(IOI)的读者可能已经发现了,选择Dis值最小的点时,暴力枚举过于浪费,为了避免超时,我们可以用堆(或者线段树)优化为$ o(Vlog(E)) $;

每次选择时,直接访问堆的根节点,将其弹出,并把更新的节点压入堆中;(每次压入堆中不必要判是否已入堆,只需要开个bool数组记录该点是否已有最短路即可)

(甚至可以用斐波那契堆优化为$ o(E+Vlog(V)) $)

附上丑陋的代码(CodeChef CLIQUED):

 1 #include<cstdio>
 2 #include<queue>
 3 #include<iostream>
 4 using namespace std;
 5 const int MAXN=1600010;
 6 const long long INF=1e15+10;
 7 struct node
 8 {
 9     int pos;
10     long long dis;
11     bool operator <(const node &x)const
12     {
13         return x.dis<dis;
14     }
15 };
16 priority_queue<node> q;
17 int tot,first[MAXN],nxt[MAXN],last[MAXN],to[MAXN],len[MAXN];
18 //平平无奇的邻接表 
19 int T,n,k,x,m,s;
20 long long dis[MAXN];
21 //dis为到每个点的最短路径 
22 bool vis[MAXN];
23 //vis记录每个点是否已有最短路的值 
24 void dijistra()
25 {
26     dis[s]=0;
27     q.push((node){s,0}); 
28     while(!q.empty())
29     {
30         node t=q.top();
31         int po=t.pos;
32         long long di=t.dis;
33         q.pop();
34         //取堆的根节点 
35         if(vis[po])
36             continue;
37         //如果已有最短路,说明该点已更新过,无需更新 
38         vis[po]=1;
39         for(int i=first[po];i;i=nxt[i])
40         if(di+len[i]<dis[to[i]])
41         {
42             dis[to[i]]=di+len[i];
43             q.push((node){to[i],di+len[i]});
44         }
45         //常规松弛(更新最短路径) 
46     }
47 }
48 void add(int x,int y,int z)
49 {
50     tot++;
51     if(first[x]==0)
52         first[x]=tot;
53     else
54         nxt[last[x]]=tot;
55     last[x]=tot;
56     to[tot]=y;
57     len[tot]=z;
58 }
59 int main()
60 {
61     scanf("%d",&T);
62     for(int ii=1;ii<=T;++ii)
63     {
64         scanf("%d%d%d%d%d",&n,&k,&x,&m,&s);
65         n++;
66         for(int i=1;i<=n;++i)
67             first[i]=0;
68         for(int i=1;i<=m;++i)
69         {
70             int x,y,z;
71             scanf("%d%d%d",&x,&y,&z);
72             add(x,y,z);
73             add(y,x,z);
74         }
75         for(int i=1;i<=k;++i)
76         {
77             add(i,n,x);
78             add(n,i,0);
79         }
80         for(int i=1;i<=n;++i)
81             dis[i]=INF;
82         for(int i=1;i<=n;++i)
83             vis[i]=0;
84         dijistra();
85         for(int i=1;i<n;++i)
86             printf("%lld ",dis[i]);
87         printf("\n");
88     }
89     return 0;
90 }

 

转载于:https://www.cnblogs.com/JinLeiBo/p/11189594.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值