Dijkstra最短路算法
带负权则无法处理,不能使用Dijkstra算法
Dijkstra算法以 点 出发。D——点从剩下的点里的最短路dis最小的出发
SPFA单源最短路算法
算是bellman-ford算法
对于稀疏图来说,比Dijkstra算法快
SPFA算法可以用于有负权图有负环则不行
一般能用Dijstra算法则用Dijstra算法
SPFA算法以边出发bellman-ford,b——边
SPFA用到队列,将源点相关联的点放到队列中然后再从队列中的点取寻找
SPFA判断负环
Floyd多源最短路算法
只能处理不带负边权的图
用邻接矩阵而不是邻接表
注意循环的顺序:
- 先循环k
- 再循环i
- 最后才是j
可以理解为:先固定一个点,这个点是否出现最短路中。然后再固定 i , j 观察路径,通过枚举出任意两个点的路径,然后再看固定的k点是否出现在其中这样的循环效率更高
这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
次短路问题
灾后重建
B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
给出 B 地区的村庄数
N
N
N,村庄编号从
0
0
0 到
N
−
1
N-1
N−1,和所有
M
M
M 条公路的长度,公路是双向的。并给出第
i
i
i 个村庄重建完成的时间
t
i
t_i
ti,你可以认为是同时开始重建并在第
t
i
t_i
ti 天重建完成,并且在当天即可通车。若
t
i
t_i
ti 为
0
0
0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有
Q
Q
Q 个询问
(
x
,
y
,
t
)
(x,y,t)
(x,y,t),对于每个询问你要回答在第
t
t
t 天,从村庄
x
x
x 到村庄
y
y
y 的最短路径长度为多少。如果无法找到从
x
x
x 村庄到
y
y
y 村庄的路径,经过若干个已重建完成的村庄,或者村庄
x
x
x 或村庄
y
y
y 在第
t
t
t 天仍未重建完成,则需要返回 -1
。
输入格式
第一行包含两个正整数 N , M N,M N,M,表示了村庄的数目与公路的数量。
第二行包含 N N N个非负整数 t 0 , t 1 , … , t N − 1 t_0, t_1,…, t_{N-1} t0,t1,…,tN−1,表示了每个村庄重建完成的时间,数据保证了 t 0 ≤ t 1 ≤ … ≤ t N − 1 t_0 ≤ t_1 ≤ … ≤ t_{N-1} t0≤t1≤…≤tN−1。
接下来 M M M行,每行 3 3 3个非负整数 i , j , w i, j, w i,j,w, w w w为不超过 10000 10000 10000的正整数,表示了有一条连接村庄 i i i与村庄 j j j的道路,长度为 w w w,保证 i ≠ j i≠j i=j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是 M + 3 M+3 M+3行包含一个正整数 Q Q Q,表示 Q Q Q个询问。
接下来 Q Q Q行,每行 3 3 3个非负整数 x , y , t x, y, t x,y,t,询问在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少,数据保证了 t t t是不下降的。
输出格式
共 Q Q Q行,对每一个询问 ( x , y , t ) (x, y, t) (x,y,t)输出对应的答案,即在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少。如果在第t天无法找到从 x x x村庄到 y y y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄 y y y在第 t t t天仍未修复完成,则输出 − 1 -1 −1。
样例输入 #1
4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4
样例输出 #1
-1
-1
5
4
提示
对于 30 % 30\% 30%的数据,有 N ≤ 50 N≤50 N≤50;
对于 30 % 30\% 30%的数据,有 t i = 0 t_i= 0 ti=0,其中有 20 % 20\% 20%的数据有 t i = 0 t_i = 0 ti=0且 N > 50 N>50 N>50;
对于 50 % 50\% 50%的数据,有 Q ≤ 100 Q≤100 Q≤100;
对于 100 % 100\% 100%的数据,有 N ≤ 200 N≤200 N≤200, M ≤ N × ( N − 1 ) / 2 M≤N \times (N-1)/2 M≤N×(N−1)/2, Q ≤ 50000 Q≤50000 Q≤50000,所有输入数据涉及整数均不超过 100000 100000 100000。
思路
- 比较典型的 Floyd 算法问题
- 具体见代码注释吧
题解
#include<bits/stdc++.h>
using namespace std;
int n,m,a[205],f[205][205];
inline void update(int k){
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",a+i);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++) f[i][j]=1e9;
for(int i=0;i<n;i++) f[i][i]=0;
int s1,s2,s3;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&s1,&s2,&s3);
f[s1][s2]=f[s2][s1]=s3;
}
int q;
scanf("%d",&q);
int now=0;
for(int i=1;i<=q;i++){
scanf("%d%d%d",&s1,&s2,&s3);
while(a[now]<=s3&&now<n){ //因为题目中说明了是按照时间顺序的,所以不用排序。用now作Floyd算法中的k,因为在给出的询问中是按时间顺序的,所以时间早的,也早就被更新了。只用一步一步更新后来的时间对应的城镇即可
update(now);
now++;
}
if(a[s1]>s3||a[s2]>s3)cout<<-1<<endl;
else {
if(f[s1][s2]==1e9)cout<<-1<<endl;
else cout<<f[s1][s2]<<endl;
}
}
}
邮递员送信
有一个邮递员要送东西,邮局在节点 1 1 1。他总共要送 n − 1 n-1 n−1 样东西,其目的地分别是节点 2 2 2 到节点 n n n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m m m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n − 1 n-1 n−1 样东西并且最终回到邮局最少需要的时间。
输入格式
第一行包括两个整数, n n n 和 m m m,表示城市的节点数量和道路数量。
第二行到第 ( m + 1 ) (m+1) (m+1) 行,每行三个整数, u , v , w u,v,w u,v,w,表示从 u u u 到 v v v 有一条通过时间为 w w w 的道路。
输出格式
输出仅一行,包含一个整数,为最少需要的时间。
样例输入 #1
5 10
2 3 5
1 5 5
3 5 6
1 2 8
1 3 8
5 3 4
4 1 8
4 5 3
3 5 6
5 4 2
样例输出 #1
83
提示
对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 200 1 \leq n \leq 200 1≤n≤200。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 3 1 \leq n \leq 10^3 1≤n≤103, 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1≤m≤105, 1 ≤ u , v ≤ n 1\leq u,v \leq n 1≤u,v≤n, 1 ≤ w ≤ 1 0 4 1 \leq w \leq 10^4 1≤w≤104,输入保证任意两点都能互相到达。
思路
- 看完题目,可以得知:要实现邮递员从 1 到 n-1 是比较简单的,基本就是套模板
- 根据题目不难得知是有向边。从 n-1 到 1 ,如果用spfa算法遍历 n-1 到 1 的话,时间复杂度是O(n3),大概率TLE。所以不先考虑这个方法
- 考虑建立反图:在邮递员回去的这段中,将图上所有的边取反向,那么从 1 到 n-1 的最短路即是 非反图中 n-1 到 1 的最短路
题解
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,u[100005],v[100005],w[100005],dis[1005],ans=0;
void ford(){
for(int i=1;i<=n;i++)dis[i]=INF;
dis[1]=0;
for(int k=1;k<=n-1;k++){
for(int i=1;i<=m;i++){
if(dis[v[i]]>dis[u[i]]+w[i]){
dis[v[i]]=dis[u[i]]+w[i];
}
}
}
for(int i=1;i<=n;i++)ans+=dis[i];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d%d%d",&u[i],&v[i],&w[i]);
ford();
for(int i=1;i<=m;i++) swap(u[i],v[i]);
ford();
printf("%d\n",ans);
}
【模板】单源最短路径(标准版)
2018 年 7 月 19 日,某位同学在 NOI Day 1 T1 归程 一题里非常熟练地使用了一个广为人知的算法求最短路。
然后呢?
100 → 60 100 \rightarrow 60 100→60;
Ag → Cu \text{Ag} \rightarrow \text{Cu} Ag→Cu;
最终,他因此没能与理想的大学达成契约。
小 F 衷心祝愿大家不再重蹈覆辙。
给定一个 n n n 个点, m m m 条有向边的带非负权图,请你计算从 s s s 出发,到每个点的距离。
数据保证你能从 s s s 出发到任意点。
输入格式
第一行为三个正整数
n
,
m
,
s
n, m, s
n,m,s。
第二行起
m
m
m 行,每行三个非负整数
u
i
,
v
i
,
w
i
u_i, v_i, w_i
ui,vi,wi,表示从
u
i
u_i
ui 到
v
i
v_i
vi 有一条权值为
w
i
w_i
wi 的有向边。
输出格式
输出一行 n n n 个空格分隔的非负整数,表示 s s s 到每个点的距离。
样例输入 #1
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
样例输出 #1
0 2 4 3
提示
样例解释请参考 数据随机的模板题。
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105;
1 ≤ m ≤ 2 × 1 0 5 1 \leq m \leq 2\times 10^5 1≤m≤2×105;
s = 1 s = 1 s=1;
1 ≤ u i , v i ≤ n 1 \leq u_i, v_i\leq n 1≤ui,vi≤n;
0 ≤ w i ≤ 1 0 9 0 \leq w_i \leq 10 ^ 9 0≤wi≤109,
0 ≤ ∑ w i ≤ 1 0 9 0 \leq \sum w_i \leq 10 ^ 9 0≤∑wi≤109。
思路
- 模板题,用好Dijkstra算法+堆优化
- 虽然Dijkstra算法思想好理解,但是实现起来还是有点复杂的
- 具体看代码注释
题解
#include<bits/stdc++.h>
const int MaxN = 100010, MaxM = 500010;
struct edge{
int to, dis, next;
};
edge e[MaxM];
int head[MaxN], dis[MaxN], cnt;
bool vis[MaxN];
int n, m, s;
inline void add_edge( int u, int v, int d ){
cnt++;
e[cnt].dis = d;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt; //这个head以链表的形式连接点,分析下面给出的图就会发现,通过head数组即可找到u点连接的所有点
}
struct node{
int dis;
int pos;
bool operator <( const node &x )const
{
return x.dis < dis;
}
};
std::priority_queue<node> q;
inline void dijkstra(){
dis[s] = 0;
q.push( ( node ){0, s} );
while( !q.empty() ){
node tmp = q.top();
q.pop();
int x = tmp.pos, d = tmp.dis;
if( vis[x] )continue;
vis[x] = 1;
for( int i = head[x]; i; i = e[i].next ){ //循环:i=e[i].next就可以发现相关联的点都被连接起来了
int y = e[i].to;
if( dis[y] > dis[x] + e[i].dis ){
dis[y] =std::min(dis[y],dis[x] + e[i].dis);
if( !vis[y] ) q.push( ( node ){dis[y], y} );
}
}
}
}
int main(){
scanf( "%d%d%d", &n, &m, &s );
for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
for( register int i = 0; i < m; ++i ){
register int u, v, d;
scanf( "%d%d%d", &u, &v, &d );
add_edge( u, v, d );
}
dijkstra();
for( int i = 1; i <= n; i++ )
printf( "%d ", dis[i] );
return 0;
}
感觉Dijkstra算法代码和SPFA代码有些相似之处
【模板】负环
给定一个 n n n 个点的有向图,请求出图中是否存在从顶点 1 1 1 出发能到达的负环。
负环的定义是:一条边权之和为负数的回路。
输入格式
本题单测试点有多组测试数据
输入的第一行是一个整数 T T T,表示测试数据的组数。对于每组数据的格式如下:
第一行有两个整数,分别表示图的点数 n n n 和接下来给出边信息的条数 m m m。
接下来 m m m 行,每行三个整数 u , v , w u, v, w u,v,w。
- 若 w ≥ 0 w \geq 0 w≥0,则表示存在一条从 u u u 至 v v v 边权为 w w w 的边,还存在一条从 v v v 至 u u u 边权为 w w w 的边。
- 若 w < 0 w < 0 w<0,则只表示存在一条从 u u u 至 v v v 边权为 w w w 的边。
输出格式
对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES
,否则输出 NO
。
样例输入 #1
2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8
样例输出 #1
NO
YES
提示
数据规模与约定
对于全部的测试点,保证:
- 1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1≤n≤2×103, 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1≤m≤3×103。
- 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n, − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 −104≤w≤104。
- 1 ≤ T ≤ 10 1 \leq T \leq 10 1≤T≤10。
提示
请注意, m m m 不是图的边数。
思路
- 我们发现第 i i i轮迭代实际是在计算最短路包含 i i i条边的结点。所以我们用 c n t [ x ] cnt[x] cnt[x]表示1到 x x x的最短路包含的边数, c n t cnt cnt[1]=0。每次用 d i s [ x ] + w ( x , y ) dis[x]+w(x,y) dis[x]+w(x,y)更新 d i s [ y ] dis[y] dis[y],也用 c n t [ x ] cnt[x] cnt[x]+1更新 c n t [ y ] cnt[y] cnt[y]。此过程中若出现 c n t [ y ] cnt[y] cnt[y]≥ n n n则图中有负环。最坏情况复杂度也是 O ( n m ) O(nm) O(nm)
- 也可以用另一种方式理解: 1 1 1到 x x x的最短路上的边数一定不多于 n − 1 n-1 n−1。否则至少有一个结点被重复经过,这说明存在环,且经过该环能更新该结点的 d i s dis dis值,即存在负环
- SPFA也可以通过记录每个点的入队次数判断负环,若有节点入队次数 ≥n,则有负环。这种方式效率会略低
- 具体看代码解析
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+10;
const int maxm=6e3+10;
int n,m;
struct Edge{
int to,w,next;
}edge[maxm];
int head[maxn],tot;
inline void Init(){
for(int i=0;i<maxn;i++) head[i]=0;
tot=0;
}
inline void addedge(int u,int v,int w){
edge[++tot].to=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot; //仍然是用head来作为链表存储
}
queue<int> Q;
int dis[maxn],vis[maxn],cnt[maxn];
bool spfa(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
dis[1]=0; vis[1]=true;
Q.push(1);
while(!Q.empty()){
int x=Q.front();
Q.pop();
vis[x]=false; //因为环,所以弹出x后将vis[x]置于未标记
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to,z=edge[i].w;
if(dis[y]>dis[x]+z){
dis[y]=dis[x]+z;
cnt[y]=cnt[x]+1;
if(cnt[y]>=n) return true; //相比上一题,这题就是多加了一步判断负环
if(!vis[y]) Q.push(y),vis[y]=true;
}
}
}
return false;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
Init();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
if(w>=0) addedge(v,u,w);
}
puts(spfa()?"YES":"NO"); //这个输出方式很新颖
}
}
集合位置
每次有大的活动,大家都要在一起“聚一聚”,不管是去好乐迪,还是避风塘,或者汤姆熊,大家都要玩的痛快。还记得心语和花儿在跳舞机上的激情与释放,还记得草草的投篮技艺是如此的高超,还记得狗狗的枪法永远是’S’……还有不能忘了,胖子的歌声永远是让我们惊叫的!!
今天是野猫的生日,所以想到这些也正常,只是因为是上学日,没法一起去玩了。但回忆一下那时的甜蜜总是一种幸福嘛。。。
但是每次集合的时候都会出现问题!野猫是公认的“路盲”,野猫自己心里也很清楚,每次都提前出门,但还是经常迟到,这点让大家很是无奈。后来,野猫在每次出门前,都会向花儿咨询一下路径,根据已知的路径中,总算能按时到了。
现在提出这样的一个问题:给出 n n n 个点的坐标,其中第一个为野猫的出发位置,最后一个为大家的集合位置,并给出哪些位置点是相连的。野猫从出发点到达集合点,总会挑一条最近的路走,如果野猫没找到最近的路,他就会走第二近的路。请帮野猫求一下这条第二最短路径长度。
输入格式
第一行是两个整数 n ( 1 ≤ n ≤ 200 ) n(1 \le n \le 200) n(1≤n≤200) 和 m m m,表示一共有 n n n 个点和 m m m 条路,以下 n n n 行每行两个数 x i x_i xi, y i y_i yi, ( − 500 ≤ x i , y i ≤ 500 ) , (-500 \le x_i,y_i \le 500), (−500≤xi,yi≤500), 代表第 i i i 个点的坐标,再往下的 m m m 行每行两个整数 p j p_j pj, q j , ( 1 ≤ p j , q j ≤ n ) q_j,(1 \le p_j,q_j \le n) qj,(1≤pj,qj≤n),表示两个点相通。
输出格式
只有一行包含一个数,为第二最短路线的距离(保留两位小数),如果存在多条第一短路径,则答案就是第一最短路径的长度;如果不存在第二最短路径,输出 -1
。
样例输入 #1
3 3
0 0
1 1
0 2
1 2
1 3
2 3
样例输出 #1
2.83
思路
- 找出最短路,然后枚举删除最短路上的每一条边,即可知道次短路的长度
题解
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pdi pair<double,int>
using namespace std;
struct Node{
double x,y;
int head;
double dis;
int prev;
}node[205];
struct Edge{
int next,to;
double len;
}edge[50005];
int n,m,cnt;
double ans=INF<<1;
double calc(double a,double b,double c,double d){
return (double)sqrt(double(a-c)*double(a-c)+double(b-d)*double(b-d));
}
void addEdge(int u,int v,double w){
edge[++cnt].len=w;
edge[cnt].to=v;
edge[cnt].next=node[u].head;
node[u].head=cnt;
}
void Dijkstra(int x,int y){
for(int i=1;i<=n;i++) node[i].dis=INF;
node[1].dis=0;
priority_queue<pdi,vector<pdi>,greater<pdi> >q;
q.push({0,1});
while(q.size()){
pdi tmp=q.top();
q.pop();
double d=tmp.first;
int u=tmp.second;
if(node[u].dis!=d)continue;
for(int e=node[u].head;e;e=edge[e].next){
int v=edge[e].to;
if((u==x&&v==y)||(u==y&&v==x))continue;
if(node[v].dis<=d+edge[e].len) continue;
if(x==-1&&y==-1)node[v].prev=u;
node[v].dis=d+edge[e].len;
q.push({node[v].dis,v});
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&node[i].x,&node[i].y);
}
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
double w=calc(node[u].x,node[u].y,node[v].x,node[v].y);
addEdge(u,v,w);
addEdge(v,u,w);
}
Dijkstra(-1,-1);
for(int i=n;i!=1;i=node[i].prev){
Dijkstra(i,node[i].prev);
ans=min(ans,node[n].dis);
}
if(ans>=INF)puts("-1");
else printf("%.2lf\n",ans);
}