[自存]最短路模版大全!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\) 个未知数的形如:

\[\begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases} \]

的不等式组,求任意一组满足这个不等式组的解。

输入格式

第一行为两个正整数 \(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 补充

为什么这里没有放美图?
赶紧放一张

image

posted @ 2025-03-03 20:13  elainafan  阅读(34)  评论(0)    收藏  举报