[自存]最短路模版大全!WOW!
也是刷了一段时间的洛谷最短路题单了 感觉该刷的模版差不多也都刷完了
今天来做个总结叭
Part 1:单源最短路算法
首先是dijkstra算法 该算法对于负边、重边会被卡 但是在正向边的情况是比较快的算法
时间复杂度为O(mlogn)
直接看代码吧(优先队列优化的):
#include<bits/stdc++.h>
using namespace std;
int n,m,s;
int head[1000005],ver[1000005],nex[1000005],d[100005],vis[100005],edge[1000005];
int x,y,z,tot;
const int INF=2147483647;
priority_queue<pair<int,int>>q;
void add(int x,int y,int z){
ver[++tot]=y;edge[tot]=z;
nex[tot]=head[x];head[x]=tot;
}
void dijkstra(){
q.push(make_pair(0,s));
while(!q.empty()){
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z){
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=1;i<=n;i++){
d[i]=INF,vis[i]=0;
}
d[s]=0;
dijkstra();
for(int i=1;i<=n;i++){
cout<<d[i]<<' ';
}
system("pause");
return 0;
}
然后是spfa算法(她死了),可以处理负环和重边,但是极端情况(如菊花图)会被卡
时间复杂度为O(km) k为一个较小的常数 菊花图会退化成Bellmen-ford算法 时间复杂度为O(mn)
看算法:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,tot;
int d[100005],v[100005];//v[x]表示x在不在队列中,spfa和dijkstra最大的差别就是一个是队列 另一个大根堆 用的是vis表示是否走过 且vis变为1后不会变化
int x,y,z;
int nex[100005],head[100005],ver[100005],edge[100005];
int sx;
queue<int>q;
void add(int x,int y,int z){
ver[++tot]=y;
edge[tot]=z;
nex[tot]=head[x];
head[x]=tot;
}
void spfa(int sx){
memset(d,0x3f,sizeof(d));
memset(v,0,sizeof(v));
d[sx]=0;v[sx]=1;
q.push(sx);
while(!q.empty()){
int x=q.front();q.pop();
v[x]=0;
for(int i=head[x];i;i=nex[i]){
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z){
d[y]=d[x]+z;
if(!v[y]){
q.push(y);
v[y]=1;
}
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
scanf("%d",&sx);
spfa(sx);
for(int i=1;i<=n;i++){
printf("%d ",d[i]);
}
system("pause");
return 0;
}
Part 2 全源最短路径
这里不介绍Johnson算法 原因很简单就是还没刷到哈哈
我们介绍一下Floyd算法 它的时间复杂度为O(\(n^3\))
所以看到比较小的数据的时候可以使用哦
有点类似于DP
直接上代码吧:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
int ma[1005][1005];
int dp[1005][1005];
int a[10005];
ll ans;
int main(){
memset(dp,0x3f,sizeof(dp));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&dp[i][j]);
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
}
}
for(int i=1;i<=m-1;i++){
ans+=dp[a[i]][a[i+1]];
}
printf("%lld",ans);
system("pause");
return 0;
}
Part 3 衍生的模版
Section A 负环
前面提到 SPFA算法可以处理负环 那么要怎么处理呢?
答案是 用计数数组计数入队次数(不是松弛次数!) 然后开心地如果>=n+1就有负环
看代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int t,n,m,tot;
int u,v,w;
int head[20005],nex[20005],ver[20005],edge[20005];
int cnt[20005],vis[20005],d[20005];
void add(int x,int y,int z){
ver[++tot]=y;
edge[tot]=z;
nex[tot]=head[x];
head[x]=tot;
}
void spfa(int sx){
queue<int>q;
d[sx]=0,vis[sx]=1,cnt[sx]=1;
q.push(sx);
while(!q.empty()){
int x=q.front();q.pop();
vis[x]=0;
for(int i=head[x];i;i=nex[i]){
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z){
d[y]=d[x]+z;
if(!vis[y]){
cnt[y]++;
if(cnt[y]>=n){//这里判断的应该是入队次数而不是松弛次数
printf("YES\n");
return ;
}
vis[y]=1;
q.push(y);
}
}
}
}
printf("NO\n");
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
memset(head,0,sizeof(head));
memset(nex,0,sizeof(nex));
memset(ver,0,sizeof(ver));
memset(edge,0,sizeof(edge));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
tot=0;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
if(w>=0){
add(u,v,w);
add(v,u,w);
}
else{
add(u,v,w);
}
}
spfa(1);
}
system("pause");
return 0;
}
Section B 传递闭包
其实就是对于一个要么0要么1的传递关系
检验任意两点间是否有传递性
用Floyd跑
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
int ma[105][105];
int dp[105][105];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&ma[i][j]);
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ma[i][j]|=ma[i][k]&ma[k][j];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",ma[i][j]);
}
cout<<endl;
}
system("pause");
return 0;
}
Section C 差分约束
我们把差分约束的题面放上来:
P5960 【模板】差分约束
题目描述
给出一组包含 \(m\) 个不等式,有 \(n\) 个未知数的形如:
的不等式组,求任意一组满足这个不等式组的解。
输入格式
第一行为两个正整数 \(n,m\),代表未知数的数量和不等式的数量。
接下来 \(m\) 行,每行包含三个整数 \(c,c',y\),代表一个不等式 \(x_c-x_{c'}\leq y\)。
输出格式
一行,\(n\) 个数,表示 \(x_1 , x_2 \cdots x_n\) 的一组可行解,如果有多组解,请输出任意一组,无解请输出 NO。
输入输出样例 #1
输入 #1
3 3
1 2 3
2 3 -2
1 3 1
输出 #1
5 3 5
说明/提示
样例解释
\(\begin{cases}x_1-x_2\leq 3 \\ x_2 - x_3 \leq -2 \\ x_1 - x_3 \leq 1 \end{cases}\)
一种可行的方法是 \(x_1 = 5, x_2 = 3, x_3 = 5\)。
\(\begin{cases}5-3 = 2\leq 3 \\ 3 - 5 = -2 \leq -2 \\ 5 - 5 = 0\leq 1 \end{cases}\)
数据范围
对于 \(100\%\) 的数据,\(1\leq n,m \leq 5\times 10^3\),\(-10^4\leq y\leq 10^4\),\(1\leq c,c'\leq n\),\(c \neq c'\)。
评分策略
你的答案符合该不等式组即可得分,请确保你的答案中的数据在 int 范围内。
如果并没有答案,而你的程序给出了答案,SPJ 会给出 There is no answer, but you gave it,结果为 WA;
如果并没有答案,而你的程序输出了 NO,SPJ 会给出 No answer,结果为 AC;
如果存在答案,而你的答案错误,SPJ 会给出 Wrong answer,结果为 WA;
如果存在答案,且你的答案正确,SPJ 会给出 The answer is correct,结果为 AC。
Solution
好啦,你要怎么办?
具体解释可以看大佬的喔 我只做算法实现的说明
由于有负环 所以我们采用SPFA算法跑最长路(注意 是最长路)
构建一个超级源点0 0到每个点都连一条权值为0的边
建图的时候建(a,b,-c),注意是-c
然后以0为起点跑一遍最长路 如果存在负环就无解 反之0到每个点的最长路就是一组解
2025.04.03 补充
我们在某些题目中 为了确保连通性 需要建立超级源点
诶?上面好像说过了
然后 要注意题目有没有什么“至少有一个答案要是0”这种特殊处理
看代码叭:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,tot;
int x,y,z;
int flag=0;
int head[20005],nex[20005],edge[20005],ver[20005];
int d[20005],v[20005],cnt[20005];
queue<int>q;
void add(int x,int y,int z){
ver[++tot]=y;
edge[tot]=z;
nex[tot]=head[x];
head[x]=tot;
}
void spfa(int sx){
memset(d,-1,sizeof(d));
memset(v,0,sizeof(v));
d[0]=0,v[0]=1;cnt[0]=1;
q.push(0);
while(!q.empty()){
int x=q.front();q.pop();
v[x]=0;
for(int i=head[x];i;i=nex[i]){
int y=ver[i],z=edge[i];
if(d[y]<d[x]+z){
d[y]=d[x]+z;
if(!v[y]){
cnt[y]++;
if(cnt[y]>n+1){
flag=1;
return ;
}
v[y]=1;
q.push(y);
}
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
add(0,i,0);
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,-z);
}
spfa(0);
if(flag==1){
cout<<"NO";
system("pause");
return 0;
}
for(int i=1;i<=n;i++){
cout<<d[i]<<' ';
}
system("pause");
return 0;
}
好啦 终于讲完了
2025.04.03 补充
为什么这里没有放美图?
赶紧放一张


浙公网安备 33010602011771号