最短路再放送

前言

当我做另一道题使用最短路算法时,我习惯性地去\(Luogu\)博找\(Dijksttra\)的板子,这时候我觉得这种算法应该记住,于是我又重温了一遍最短路算法,发现了很多困惑,尤其是关于\(SPFA\)\(Dijkstra\)算法的区别。因为我的\(SPFA\)\(Dijkstra\)都使用了堆优化。
然后我困惑了很久,终于明白了,准备推样例理解一下,又心血来潮重温了一下\(Dijkstra\)算法为什么不能处理负权边,然后构造了一个带负权的有向图,结果呢。
我的\(Dijkstra\)竟然跑出了正确的答案!
我惊了,拿朋友的板子发现正确的\(Dijkstra\)算法是不能跑负权边的,于是我再去看我的板子,怀疑我的\(Dijkstra\)写成了\(SPFA\),要是我发现了能处理负权边的\(Dijkstra\)算法,我估计会被保送吧哈哈。于是改掉了我的板子,所幸在\(NOIP\)前发现,在此简单总结一下最短路算法。

\(Floyed\)

多源最短路算法,运用了松弛的思想,有很浓重的\(DP\)气息(因为它就是\(DP\)),通过枚举中间点转移。
复杂度\(O(n^3)\),在解决最短路问题上基本没有用处,如果解决多源问题完全可以跑\(n\)\(Dijkstra\),和矩阵乘法结合有一些用,具体我也忘了。

memset(f,inf,sizeof(f));
for(int i=1;i<=n;++i) f[i][i]=0;
for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
        for(int k=1;k<=n;++k)
          f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

\(SPFA\)

\(SPFA\)是个随机数据下比较快的算法,它的思想是每次从队列中找到一个点,松弛它所连的边,然后出队,注意这个点是可以再次被更新的,也就是可以再次入队重新松弛,不断逼近,直到最后求出最优解。所以卡\(SPFA\)的办法就是让\(SPFA\)跑到终点,然后再从起点更新,让它不停地重复跑就可以了,具体方法是构造一个网格图,将横向边边权设为很小,纵边很大即可。
\(SPFA\)最优复杂度为\(O(KE)\),其中\(K\)是一个常数,但是最坏情况可以达到\(O(VE)\)
众所周知,现在很多最短路题目出题人都会卡\(SPFA\),用双端队列优化或者是堆优化本质都是不变的,都会被卡,所以还是用\(Dijkstra\)吧。
常规的\(SPFA\)

#include<bits/stdc++.h>
#define N 10005
#define maxn 500005
#define INF 2147483647
using namespace std;
int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;
bool vis[N];
struct Edge{
    int next,w,v;
}e[maxn];
inline void add(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
    return;
}
void spfa(int s){
    for(int i=1;i<=n;i++){
        dis[i]=INF;
        vis[i]=true;
    }
    dis[s]=0;
    queue <int> q;
    q.push(s);
    vis[s]=false;
    while(!q.empty()){
        eu=q.front();
        q.pop();
        vis[eu]=true;
        for(int i=head[eu];i;i=e[i].next){
            ev=e[i].v;
            if(dis[eu]+e[i].w<dis[ev]){
                dis[ev]=dis[eu]+e[i].w;
                if(vis[ev]){
                    q.push(ev);
                    vis[ev]=false;
                }
            }
        }
    }
}
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int main()
{
    cin>>n>>m>>s;
    for(register int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    spfa(s);
    for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
}

比较常用的双端队列优化(\(SLF\)优化),大概思想是队列为空或者当前最短距离比队首小时放到队首,否则放到队首,最好再熟悉一下\(STL\)

#include<bits/stdc++.h>
#define N 100005
#define maxn 200005
#define INF 2147483647
using namespace std;
int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;
bool vis[N];
struct Edge{
    int next,w,v;
}e[maxn];
inline void add(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
    return;
}
void spfa(int s){
    for(int i=1;i<=n;i++){
        dis[i]=INF;
    }
    dis[s]=0;
    deque <int> q;
    q.push_back(s);
    vis[s]=1;
    while(!q.empty()){
        eu=q.front();
        q.pop_front();
        vis[eu]=0;
        for(int i=head[eu];i;i=e[i].next){
            ev=e[i].v;
            if(dis[eu]+e[i].w<dis[ev]){
                dis[ev]=dis[eu]+e[i].w;
                if(!vis[ev]){
                    vis[ev]=1;
//                    if(dis[q.front()] > dis[q.back()]) {
//                      int fr = q.front() ; q.pop_front() ;
//                      int ba = q.back() ; q.pop_back() ;
//                      q.push_front(ba), q.push_back(fr) ;
//                  }
                    if(q.empty()||dis[ev]<dis[q.front()]) q.push_front(ev);
                    else q.push_back(ev);
                }
            }
        }
    }
}
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int main()
{
    cin>>n>>m>>s;
    for(register int i=1;i<=m;i++)
    {
        x = read(), y = read(), z = read() ;
        add(x,y,z);
    }
    spfa(s);
    for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
}

\(SPFA\)堆优化:算是比较快的,但是不常用,这个可以过掉洛谷的单源最短路(标准版),而且比\(Dijkstra\)要快

#include<bits/stdc++.h>
#define N 200000 + 10
#define maxn 200000 + 10
#define INF 2147483647
using namespace std;
int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;
bool vis[N];
struct Edge{
    int next,w,v;
}e[maxn];
inline void add(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
    return;
}
struct cmp{
    bool operator ()(int &x, int &y)
    {
        return dis[x] > dis[y];
    }
};
void spfa(int s)
{
    for(int i = 1; i <= n; i++) dis[i] = INF;
    dis[s]=0;
    priority_queue<int, vector<int>, cmp> q;
    q.push(s);
    vis[s]=1;
    while(!q.empty()){
        eu=q.top();
        q.pop();
        vis[eu]=0;
        for(int i=head[eu];i;i=e[i].next){
            ev=e[i].v;
            if(dis[eu]+e[i].w<dis[ev]){
                dis[ev]=dis[eu]+e[i].w;
                if(!vis[ev]){
                    vis[ev]=1;
                    q.push(ev);
                }
            }
        }
    }
}
inline int read() {
    char ch = getchar(); int u = 0, f = 1;
    while (!isdigit(ch)) {if (ch == '-')f = -1; ch = getchar();}
    while (isdigit(ch)) {u = u * 10 + ch - 48; ch = getchar();}
    return u * f;
}
int main()
{
    n = read(); m = read(); s = read();
    for(register int i=1;i<=m;i++)
    {
        x = read(), y = read(), z = read() ;
        add(x,y,z);
    }
    spfa(s);
    for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
}

\(Dijkstra\)

\(Dijkstra\)是最常用的最短路算法,原始时间复杂度为\(O(n^2)\),堆优化后时间复杂度为\(O(nlogn)\),而且比较稳定,很难卡掉
他的思想是从已更新的点集中选出最近的点,默认这个距离为该点的最短距离,再用它来更新其它点,这个过程可以用堆优化。
“默认这个距离为该点的最短距离”这是一个贪心的思想,那么这个贪心为什么是对的呢。
尝试用反证法证明,假设\(S-->A\)不是到\(A\)的最短路,而是通过集合外的一个点作为中间节点,即\(S-->B-->A\),因为我们选择的是最近的点,所以\(S-->A\)一定小于\(S-->B\),而且\(B-->A>0\)所以\(S-->A\)\(S-->B-->A\)更优,这和假设矛盾,得证贪心成立

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define re register
#define maxn 200010

using namespace std;

int head[maxn],vis[maxn],s,cnt,dis[maxn],n,m,a,b,c;
struct Edge{
    int v,w,nxt;
}e[maxn<<2];
inline void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
struct node{
    int u,d;
    bool operator <(const node&rhs) const{
        return rhs.d<d;
    }
};
void dijkstra()
{
    //memset(dis,0x3f3f3f3f,sizeof(dis));
    dis[s]=0;
    priority_queue<node> q;
    q.push((node){s,0});
    //vis[s]=1;
    while(!q.empty())
    {
        node f=q.top();
        q.pop();
        int now=f.u,dd=f.d;
        if(vis[now])
         continue;
        vis[now]=1;
        for(int i=head[now];i;i=e[i].nxt)
        {
            int ev=e[i].v;
            if(dis[ev]>dis[now]+e[i].w)
            {
                dis[ev]=dis[now]+e[i].w;
                if(!vis[ev])
                {
                    q.push(node{ev,dis[ev]});
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for(re int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c); 
    }
    dijkstra();
    for(re int i=1;i<=n;++i)
    printf("%d ",dis[i]);
    return 0;
}

这里补一下一直困惑的结构体重组操作:

结构体重载就相当于\(cmp\)函数进行排序

bool operator <(const node&rhs) const{
        return rhs.d<d;

标准格式如上,上句话的意思是:一个结构体比另一个结构体小当且仅当这个结构体的\(d\)大于另一个结构体的\(d\)\(rhs\)相当于别的结构体
又因为堆默认大根堆,从大到小排序,那么反过来\(d\)就从小到大排序了。

转载于:https://www.cnblogs.com/Liuz8848/p/11296234.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
水资源是人类社会的宝贵财富,在生活、工农业生产中是不可缺少的。随着世界人口的增长及工农业生产的发展,需水量也在日益增长,水已经变得比以往任何时候都要珍贵。但是,由于人类的生产和生活,导致水体的污染,水质恶化,使有限的水资源更加紧张。长期以来,油类物质(石油类物质和动植物油)一直是水和土壤中的重要污染源。它不仅对人的身体健康带来极大危害,而且使水质恶化,严重破坏水体生态平衡。因此各国都加强了油类物质对水体和土壤的污染的治理。对于水中油含量的检测,我国处于落后阶段,与国际先进水平存在差距,所以难以满足当今技术水平的要求。为了取得具有代表性的正确数据,使分析数据具有与现代测试技术水平相应的准确性和先进性,不断提高分析成果的可比性和应用效果,检测的方法和仪器是非常重要的。只有保证了这两方面才能保证快速和准确地测量出水中油类污染物含量,以达到保护和治理水污染的目的。开展水中油污染检测方法、技术和检测设备的研究,是提高水污染检测的一条重要措施。通过本课题的研究,探索出一套适合我国国情的水质污染现场检测技术和检测设备,具有广泛的应用前景和科学研究价值。 本课题针对我国水体的油污染,探索一套检测油污染的可行方案和方法,利用非分散红外光度法技术,开发研制具有自主知识产权的适合国情的适于野外便携式的测油仪。利用此仪器,可以检测出被测水样中亚甲基、甲基物质和动植物油脂的污染物含量,为我国众多的环境检测站点监测水体的油污染状况提供依据。
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值