以下代码都是自己敲的,结合acwing和算法笔记的一些思路整理。禁止盗用!
有些模板算法是acwing的 做了标记。
整体思想:
1.找到与起点s最近的且未确认最短路径的顶点(记为u),访问并加入集合st(确定了最短路的集合)。
2.之后,令u为中介点,优化起点s与所有经过u可以到达的顶点v的最短距离。
算法模板:
//朴素Dijkstra 邻接矩阵存储稠密图
int g[N][N];//g[a][b]:a->b的权重
int dist[N];//dist[i]:起点到i的距离
bool st[N];//st[i]:i点是否已确定最短路
void dijkstra(int u)//u为起点
{
memset(dist,0x3f,sizeof dist);//距离都初始化为很大的数
dist[u]=0;//u->u 距离为0 起点为0
for(int i=0;idist[j]))//寻找最近的点
t=j;
st[t]=true;//找到的点 确定了最短路
for(int j=1;j<=n;j++)//遍历n个点 用最新的点更新迭代所有出边
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
}
算法题目:
849. Dijkstra求最短路 I - AcWing题库
#include
#include using namespace std; const int N=510; int g[N][N],dist[N]; bool st[N]; int n,m; void dijkstra(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; for(int i=0;i dist[j])) t=j; st[t]=true; for(int j=1;j<=n;j++) dist[j]=min(dist[j],dist[t]+g[t][j]); } } int main() { scanf("%d%d",&n,&m); memset(g,0x3f,sizeof g); while(m--) { int a,b,w; scanf("%d%d%d",&a,&b,&w); g[a][b]=min(g[a][b],w); } dijkstra(1); if(dist[n]==0x3f3f3f3f) printf("-1\n"); else printf("%d\n",dist[n]); return 0; }
算法笔记—P368 例题 亚历山大灭六国
#include
#include using namespace std; const int N=1000; int g[N][N],dist[N]; bool st[N]; int n,m,k; void dijkstra(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; for(int i=0;i dist[j])) t=j; st[t]=true; for(int j=0;j dist[t]+g[t][j]) dist[j]=dist[t]+g[t][j]; } } int main() { memset(g,0x3f,sizeof g); scanf("%d%d%d",&n,&m,&k); for(int i=0;i
我们已经求出了最短距离,那么最短路径本身我们应该怎么求呢?
在每一次u作为中间点可以更新其余顶点v的时候,可以用pre数组记录一下。(s->…->u->v)
int pre[N];//最短路径
//一开始将前驱都指向自己
for(int i=1;i<=n;i++) pre[i]=i;
//更新同时求最短路径
for(int j=1;j<=n;j++)
if(dist[j]>dist[t]+g[t][j])
{
dist[j]=dist[t]+g[t][j];
pre[j]=t;
}
但是平时比赛中,dijkstra不会就单纯考最短路径长度,或者,最短路径有多条,会增加一个第二标尺(第一标尺是距离)。第二标尺常见的是以下三种出题方法或其组合。
其实就只需要增加一个数组来存放新的数据,然后再优化dist[i]的时候增加修改即可。
1、给每条边再增加一个边权(比如说花费),要求在最短路径多条时要求路径上花费之和最小(其他含义的话也可能是最大)。
解题方法:cost[u][v]表示u->v的花费(题目给出),并增加一个数组c[ ],令从起点s到达顶点u的最少花费为c[u],初始时只有c[s]=0,其余都最大。这样就可在优化dist时,如果路径相同,取花费更少的为最优解。
int cost[N][N],c[N];//cost[a][b]:a->b的花费 c[i]:起点到i点的最少花费
memset(c,0x3f,sizeof c);
ct[u]=0; //u->u 不要花钱
for(int j=1;j<=n;j++)//遍历n个点 用最新的点更新迭代所有出边
if(dist[j]>dist[t]+g[t][j])
{
dist[j]=dist[t]+g[t][j];//以t为中介点可以使dist[j]更优
c[j]=c[t]+cost[t][j];
}else if(dist[j]==dist[t]+g[t][j] && c[j]>c[t]+cost[t][j])
c[j]=c[t]+cost[t][j];//最短距离相同看是否可以让c[v]更优
2、增加点权(每个城市能收集到物资),在最短路径多条时,要求路径点权之和最大(也可以是最小)。
解题方法:weight[u]表示城市u中的物资数量(题目给出),并增加一个数组w[ ],令从起点s到达顶点u可以收集到的最大物资为w[u],初始化时w[s]=weight[s],其余w[u]=0;更新最短路径时,更新w[u],如果最短路径长度相同,取最大者。
int weight[N],w[N];//weight[i]:i点的物资 w[i]:起点->i的最大物资量
memset(w,0,sizeof w);//物资初始化为0
w[u]=weight[u];//起点物资
for(int j=1;j<=n;j++)
{
if(dist[j]>dist[t]+g[t][j])
{
dist[j]=dist[t]+g[t][j];
w[j]=w[t]+weight[j];//更新同时更新物资
}
else if(dist[j]==dist[t]+g[t][j] && w[j]
3、求出有多少条最短路径。
解题方案:增加一个数组num[ ],令从起点s到达顶点u的最短路径条数为num[u],初始化时num[s]为1,其余num[ ]为0,更新dist[j]时,只需要让num[j]继承num[t]即可,最短距离相同时,要累加。
int num[N];//num[i]: 起点到i的最短路径条数
memset(num,0,sizeof num);//路径为0
num[u]=1;//起点一条路径 自环
for(int j=1;j<=n;j++)
{
if(dist[j]>dist[t]+g[t][j])
{
dist[j]=dist[t]+g[t][j];
num[j]=num[t];//更新同时继承路径条数
}
else if(dist[j]==dist[t]+g[t][j])
num[j]+=num[t];//最短路径长度相同,累加num
}
题目详情 - 1003 Emergency (25 分) (pintia.cn)
//城市互通 无向图!!!! 一开始看成有向图做的 #include
#include using namespace std; const int N=510; int g[N][N]; int weight[N],w[N],num[N],dist[N]; int n,m,c1,c2; bool st[N]; void dijkstra(int u) { memset(dist,0x3f,sizeof dist); memset(w,0,sizeof w); memset(num,0,sizeof num); w[u]=weight[u]; num[u]=1; dist[u]=0; for(int i=0;i dist[j])) t=j; st[t]=true; for(int j=0;j dist[t]+g[t][j]) { dist[j]=dist[t]+g[t][j]; num[j]=num[t]; w[j]=w[t]+weight[j]; } else if(dist[j]==dist[t]+g[t][j]) { num[j]+=num[t]; if(w[j]
题目详情 - 1030 Travel Plan (30 分) (pintia.cn)
#include
#include #include using namespace std; const int N=510; int n,m,s,d; int g[N][N],dist[N]; int cost[N][N],c[N],pre[N]; bool st[N]; vector path; void dijkstra(int u) { memset(dist,0x3f,sizeof dist); memset(c,0x3f,sizeof c); dist[u]=0; c[u]=0; for(int i=0;i dist[j])) t=j; st[t]=true; for(int j=0;j dist[t]+g[t][j]) { dist[j]=dist[t]+g[t][j]; pre[j]=t; c[j]=c[t]+cost[t][j]; } else if(dist[j]==dist[t]+g[t][j] && c[j]>c[t]+cost[t][j]) { c[j]=c[t]+cost[t][j]; pre[j]=t; } } } } int main() { scanf("%d%d%d%d",&n,&m,&s,&d); memset(g,0x3f,sizeof g); while(m--) { int a,b,dis,c; scanf("%d%d%d%d",&a,&b,&dis,&c); if(g[a][b]>dis) { g[a][b]=g[b][a]=dis; cost[a][b]=cost[b][a]=c; } } dijkstra(s); int t=d; while(t!=s) { path.push_back(t); t=pre[t]; } path.push_back(s); for(int i=path.size()-1;i>=0;i--) printf("%d ",path[i]); printf("%d %d",dist[d],c[d]); return 0; }
整体思想:堆优化,就是将寻找最近的未确定的点的时间复杂度变为O(1)。用邻接表来存储稀疏图,用优先队列来放堆。每次取出堆顶,然后去更新堆顶的所有出边,更新过的放入队列中。
//堆优化Dijkstra
typedef pair PII;
int n;//点数
int h[N],w[N],e[N],ne[N],idx;//邻接表的存储 w边的权重
int dist[N];
bool st[N];
void dijkstra(int u)
{
memset(dist,0x3f,sizeof dist);
dist[u]=0;
priority_queue,greater> heap;
heap.push({0,u});
while(heap.size())//堆不空
{
auto t=heap.top();//取出堆顶
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver]) continue;
//一个点可能有多个点到达,但是如果已确认最短路径,就不需要后续再去冗余
st[ver]=true;//堆顶元素 最短路径已确定
for(int i=h[ver];i!=-1;i=ne[i])//遍历所有出边
{
int j=e[ver];
if(dis[j]>dis[ver]+w[i])//更新距离
{
dis[j]=distance+w[i];
heap.push({dist[j],j});//放入堆中
}
}
}
}
850. Dijkstra求最短路 II - AcWing题库
#include
#include #include using namespace std; typedef pair PII; const int N=1.5e5+10; int n,m; int h[N],w[N],ne[N],idx,e[N]; int dist[N]; bool st[N]; void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } void dijkstra(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; priority_queue ,greater > heap; heap.push({0,u}); while(heap.size()) { auto t=heap.top(); heap.pop(); int ver=t.second,distance=t.first; if(st[ver]) continue; st[ver]=true; for(int i=h[ver];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>distance+w[i]) { dist[j]=distance+w[i]; heap.push({dist[j],j}); } } } } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); } dijkstra(1); if(dist[n]==0x3f3f3f3f) printf("-1\n"); else printf("%d\n",dist[n]); return 0; }
整体思想:两层循环,外层循环k次(k条边),内层每次遍历所有的边,并进行更新迭代。
如果外层循环第n次还有更新的话,说明存在负权回路,因为n条边,至少有n+1个点,题目中只有n个点,那么必定有两个点是相同的,那么一定存在回路。
//Bellman_Ford acwing
int n,m;
int dist[N];
struct edge{
int a,b,w;
}edge[M];//a->b的权重w
int Bellman_ford(int u)
{
memset(dist,0x3f,sizeof dist);
dist[u]=0;
for(int i=0;i0x3f3f3f3f/2) return -1;
else return dist[n];
}
算法笔记Bellman_Ford算法模板
//算法笔记模板 int n,m; int dist[N]; struct edge{ int a,b,w; }edge[M];//a->b的权重w bool Bellman_ford(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; for(int i=0;i
dist[e.a]+e.w))//如果还可以被松弛 return false;//说明图中有源点可达的负环 } return true; }
853. 有边数限制的最短路 - AcWing题库
#include
#include using namespace std; const int N=510,M=10010; int dist[N],n,m,k,backup[N];//backup 防止内循环遍历边的时候 发生串联 struct edge{ int a,b,w; }edge[M]; void Bellman_ford(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; for(int i=0;i =0x3f3f3f3f/2) printf("impossible"); // 例如 5号点和n号点 1号点都无法到达 但是在内循环 遍历边的时候 可能5号点就把n号点给更新了 else printf("%d\n",dist[n]); return 0; }
整体思想:Bellman_Ford算法每一次外层循环都需要,遍历所有的边,会有大量无意义的操作,我们可以用邻接表,存储每一个点的出边,将内存循环遍历所有边改成遍历所有出边。并且将更改的点放入队列中(只有点更改了,他的出边才有可能更改)。
SPFA模板-acwing
int n; // 总点数 int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边 int dist[N]; // 存储每个点到1号点的最短距离 bool st[N]; // 存储每个点是否在队列中 // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1 int spfa() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue
q; q.push(1); st[1] = true; while (q.size()) { auto t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入 { q.push(j); st[j] = true; } } } } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; }
851. spfa求最短路 - AcWing题库
//spfa 运用队列优化Bellman_Ford算法 发生距离更改的就入队 直到队空 #include
#include #include using namespace std; const int N=1e5+10; int h[N],ne[N],e[N],w[N],idx; int n,m; int dist[N]; bool st[N]; void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } void spfa(int u) { memset(dist,0x3f,sizeof dist); dist[u]=0; queue q; q.push(u); st[u]=true;//st[]防止重复放入队列 true 在队列中不需要在放入 while(q.size()) { int t=q.front(); q.pop(); st[t]=false; for(int i=h[t];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>dist[t]+w[i]) { dist[j]=dist[t]+w[i]; if(!st[j]) { q.push(j); st[j]=true; } } } } } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); for(int i=0;i =0x3f3f3f3f/2) printf("impossible\n"); else printf("%d\n",dist[n]); return 0; }
如果点的边数>=点数,那么一定存在回环。
原因:抽屉原理,如果有n条边,那么至少有n+1个点,题目中只有n个点,那么必定有两个点相同,那么一定会存在环。
其实就是用cnt数组来存储边数,然后对边数更新,判断。
//ACwing SPFA模板
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N], cnt[N]; // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N]; // 存储每个点是否在队列中
// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
// 不需要初始化dist数组
// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
queue q;
//题目问存在负环 并没有说从具体某个点出发存在负环,所以把所有点都放到队列中
for (int i = 1; i <= n; i ++ )
{
q.push(i);
st[i] = true;
}
while (q.size())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
852. spfa判断负环 - AcWing题库
#include
#include #include using namespace std; const int N=1e6+10; int h[N],ne[N],w[N],idx,e[N];//w[i] i点的权值 int n,m; int dis[N],cnt[N];//dis[i] 1->i 的长度 cnt[] 边数 bool st[N];//st[i] i点是否访问 void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } bool spfa() { queue q; for(int i=1;i<=n;i++) { q.push(i); st[i]=true; } while(q.size())//队列不空 { int t=q.front(); q.pop(); st[t]=false; for(int i=h[t];i!=-1;i=ne[i])//遍历所有出边 { int j=e[i]; if(dis[j]>dis[t]+w[i])//更新 { dis[j]=dis[t]+w[i]; cnt[j]=cnt[t]+1; if(cnt[j]>=n) return true;//边数大于点数 一定存在回环 if(!st[j])//点放入队列 { q.push(j); st[j]=true; } } } } return false; } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); } if(spfa()) puts("Yes"); else puts("No"); return 0; }
整体思想:如果存在顶点k,使得k作为中介点时,顶点i和顶点j的当前最短距离缩短。
三层循环,最外层时枚举终结点k,里层循环分别i,j。
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
作者:yxc
链接:https://www.acwing.com/blog/content/405/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
854. Floyd求最短路 - AcWing题库
#include
using namespace std; const int N=210,INF=1e9; int n,m,Q; int d[N][N]; void floyd() { for(int k=1;k<=n;k++)//k循环中介点 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]); } int main() { scanf("%d%d%d",&n,&m,&Q); for(int i=1;i<=n;i++)//初始化d[i][j] i->j的长度 for(int j=1;j<=n;j++) if(i==j) d[i][j]=0; else d[i][j]=INF; for(int i=0;i =INF/2) puts("impossible"); else printf("%d\n",d[x][y]); } return 0; }