最短路问题
1何为最短路
最短路算是OI上的经典问题了,最简单的问题为:在一个图上,求一个点到其他点的最短距离,即单源最短路。较为复杂的有求所有点到除它以外的其他点的最短路,即多源最短路。最短路的算法有很多,如dijkstra,floyd,bellman-ford,SPFA等,由于一些原因,除了判负环以外,SPFA基本会被卡住,因此在本文就不赘述了,本文主要讲dijkstra和floyd。
2.1dijkstra原理概述
dijkstra是对一个无负权边求单源最短路的算法,它是基于一个贪心的算法:设点A是距离原点O最近的未考虑的点,那么,设点B是另外一个未考虑的点,那么由于O点到B点的距离大于O点到A点的距离,所以即使点A到点B的距离为0,也是直接走O->A比O->B->A短。因此,我们求出A,再用A改变其他点即可。
2.2代码实现
首先看这道题:【模板】单源最短路径(弱化版)
这道题中,我们注意到n的值较小,可以用n2 的时间复杂度的算法求解,由于m的大小,我们用链式前向星记录
#include <bits/stdc++.h> using namespace std; int k,i,j,ne[500001],to[500001],he[500001],l[500001],cnt,min1,n,m,s,d[100001],x,y,z; bool b[100001]; int main() { cnt=0; cin>>n>>m>>s; for (i=1; i<=n; i++) { b[i]=true; d[i]=2147483647;//初始化,d数组记录点i到点s的距离,b数组记录该点是否被访问过 } for (i=1; i<=m; i++) { cin>>x>>y>>z; cnt++; ne[cnt]=he[x]; to[cnt]=y; l[cnt]=z; he[x]=cnt;//链式前向星储存边 } d[s]=0; d[0]=2147483647; for (i=2; i<=n; i++)//因为最后一点无需改变,所以只需n-1遍 { min1=0; for (j=1; j<=n; j++) if (b[j]&&(d[j]<=d[min1])) min1=j;//求出最近点 k=he[min1]; b[min1]=false; while (k!=0) { d[to[k]]=min(d[to[k]],d[min1]+l[k]);//修改其他点 k=ne[k]; } } for (i=1; i<=n; i++) cout<<d[i]<<' '; return 0; }
这样,这道题就做完了。
接下来,我们看这道题:【模板】单源最短路径(标准版)
在这道题中,n到了十万,显然,n2的时间复杂度太大了,考虑一下该算法的优化,我们原本用for来求最近距离,如果用堆的话,不就到nlogn了吗?这道题就能做出来了:
#include<bits/stdc++.h> using namespace std; priority_queue<pair<long long,long long> >q;//虽然是大根堆,但是第一维取负后就是小根堆了 long long n,m,s,i,cnt,x,y,z,he[100001],ne[200001],l[200001],to[200001],d[100001]; bool b[100001]; int main() { scanf("%lld%lld%lld",&n,&m,&s); for (i=1;i<=m;i++) { scanf("%lld%lld%lld",&x,&y,&z); cnt++; ne[cnt]=he[x]; to[cnt]=y; l[cnt]=z; he[x]=cnt; } memset(d,100000007,sizeof(d)); d[s]=0; q.push(make_pair(0,s));//压入s while (q.size()) { x=q.top().second;//取出最近点 q.pop(); if (b[x]) continue; b[x]=true;//记录已找 for (i=he[x];i!=0 ;i=ne[i]) if (d[to[i]]>d[x]+l[i]) { d[to[i]]=d[x]+l[i]; q.push(make_pair(-d[to[i]],to[i]));//压入堆 } } for (i=1;i<=n;i++) printf("%lld ",d[i]); return 0; }
这样,dijkstra就告一段落了,若读者想要做增强的应用题,我找出以下几道题:
(博主推销自己题解,哈哈,该博客只用来写洛谷题题解,欢迎参观)
3.1floyd原理概述
floyd是一种求多源最短路的算法,设i,j是我们要求的两点,而k是两点中的一点且与i,j联通,那么i->j的距离就是min(i->j,i->k->j)枚举i,j,k,时间复杂度为n3。
3.2代码实现
由于一下没找到例题,博主自己弄了一道:【模板】floyd
这是一道裸的floyd模板题,先看核心代码:
for (k=1;k<=n;k++)//k必须在最外面 for (i=1;i<=n;i++) for (j=1;j<=n;j++)//i,j随便 p[i][j]=min(p[i][j],p[i][k]+p[k][j]);//p[i][j]代表i到j的最短距离
由于时间复杂度较大,floyd主要求多源的问题,而且也基本无法优化了,不多说了,这是完整代码:
#include<bits/stdc++.h> using namespace std; long long n,m,p[501][501],i,j,k,ans,x,y,l; int main() { scanf("%lld%lld",&n,&m); for (i=1;i<=n;i++) for (j=1;j<=n;j++) p[i][j]=1e17;//初始化 for (i=1;i<=m;i++) { scanf("%lld%lld%lld",&x,&y,&l); p[x][y]=min(p[x][y],l);//可能有重边,取最小值 p[y][x]=p[x][y]; } for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) p[i][j]=min(p[i][j],p[i][k]+p[k][j]); for (i=1;i<=n;i++) { ans=0; for (j=1;j<=n;j++) if (j!=i) ans=(ans+p[i][j])%998244354;//嘿嘿,不是998244353哦 printf("%lld\n",ans); } return 0; }
本章就结束了,谢谢!