本题就是一道单源最短路问题。由于是稀疏图,我们采用Dijkstra算法。

Dijkstra算法原理

Dijkstra算法的步骤

我们把所有的节点分为两个集合:被选中的(visited==1) 和 未被选中的(visited==0),对于每个点,我们在操作中更新其到源点的距离。这个算法中用到贪心思想。
我们进行如下操作:
1.在所有未选中的节点中,找出目前距离源点距离最近的点,记为now,并将now移动到“被选中”集合(visited【now】=1).
2.把所有和now相连的节点next的距离进行更新,取dis【now】+l和dis【next】的最小值,从而始终维护dis数组使得其中存储了目前选取点集合中的路径最小值。
3.从s开始循环上述步骤直至进行到d。
这样,我们就得到了最短路的长度dis【d】,同时可以记录最短路的路径:只需在上述2步骤中记录下next节点的最短路是从哪个节点找到了,再回溯回去即可。

Dijkstra贪心的证明

对于贪心算法,我们在使用的时候应该考虑其正确性。
首先,dijkstra算法只适用于正边权图,图中不能存在负值边(因为dijk算法只能对未被选中的点进行距离更新,而已经选中的点在存在负边的情况下可能存在更短路)
这里不给出详细的证明,证明参见:Dijkstra算法介绍+正确性证明+性能分析_月本_诚的博客-CSDN博客_dijkstra算法正确性证明我们只需要知道,对于一条最短路,它的从s1到s2的一部分就是s1到s2的最短路。这样,前面的算法就很好理解了。
可以采用归纳法证明,假设在Dijkstra算法中找到的最短路为V:v1v2.....vn,对于i<k原算法都正确,而在i=k时不正确。
这时我们考虑到我们在维护数组时保证了i=k继承了i=k-1的最短路,因此不可能存在前半部分是最短路,后面不是的情况。

Dijkstra的代码实现

这里直接给出本题的ac代码,再分别对不同的坑点进行解释。

#include<cstdio>
#include<cstdlib>
#include<vector>
#include<queue>
#include<utility>
using namespace std;
typedef pair<int,int> P;
struct node{vector<int> next;vector<int> length;}node[30005];
vector<int> path[30005][2];
vector<int> pre[30005][2];
int sum[2],n,m,s,d,pathsum=0;
void print(int i){
printf("start\n");
int li=path[1][i].size();for(int j=li-1;j>=0;j--)printf("%d\n",path[1][i][j]);
printf("end\n");printf("%d\n",sum[i]);
}
void push(int now,int i,int ii){
pathsum=i>pathsum?i:pathsum;
path[i][ii].push_back(now);
int ll=pre[now][ii].size();
for(int j=0;j<ll;j++){
int last=pre[now][ii][j];
if(j)path[i+j][ii].push_back(now);
push(last,i+j,ii);
}
}
int dijk(int i){
bool v[30005]={0};int dis[30005];priority_queue<P,vector<P>,greater<P>> calc;
for(int i=0;i<=30000;i++){dis[i]=100000000;P x(100000000,i);calc.push(x);}
int now;P x(0,s);calc.push(x);dis[s]=0;
while(!v[d]){
int min=100000000,minj=0;
P x=calc.top();min=x.first;minj=x.second;calc.pop();
while(v[minj]){P x=calc.top();min=x.first;minj=x.second;calc.pop();}
now=minj;v[now]=1;if(min>=100000000)return 100000000;
int l=node[now].next.size();
for(int j=0;j<l;j++){
int xx=node[now].next[j],ll=node[now].length[j];
P x(dis[now]+ll,xx);calc.push(x);
if(dis[now]+ll<dis[xx]){dis[xx]=dis[now]+ll;pre[xx][i].clear();pre[xx][i].push_back(now);}
else if(dis[now]+ll==dis[xx])pre[xx][i].push_back(now);
}
}
push(d,1,i);return dis[d];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,k;scanf("%d%d%d",&x,&y,&k);
node[x].next.push_back(y);node[x].length.push_back(k);
node[y].next.push_back(x);node[y].length.push_back(k);
}
scanf("%d%d",&s,&d);
sum[1]=dijk(1);
for(int q=1;q<=pathsum;q++){
int l=path[q][1].size();
for(int i=1;i<l;i++){
int ss=path[q][1][i],dd=path[q][1][i-1];
int ll=node[ss].next.size();int deltaj=0;
for(int j=0;j<ll;j++){if(node[ss].next[j]==dd){deltaj=j;break;}}
node[ss].length[deltaj]=100000000;
int ll1=node[dd].next.size();int deltaj1=0;
for(int j=0;j<ll;j++){if(node[dd].next[j]==ss){deltaj1=j;break;}}
node[dd].length[deltaj1]=100000000;
}
}
sum[0]=dijk(0);print(1);if(sum[0]<100000000&&sum[0]>sum[1])print(0);
}

首先这道题并不是要求我们找到最短路,而是要求找到所谓的“近似最短路”
它的要求是:不与任何一条最短路的任何一条边重合
因此我们的思路是:找到所有的最短路,并删除这些边。
下面我介绍一下我在做这道题的时候遇到的一些坑点:

如何找到所有的最短路?

我发现在我的dijk算法中,只能得到一条最短路,因为我起初用一个pre数组存储n个顶点在最短路中的上一个顶点,数组大小开了n,因此每个顶点只能存唯一一个pre点,也就不能得到所有的路。
后来,我在每个顶点处都开了一个vector,来记录所有可能的pre节点。

        int l=node[now].next.size();
for(int j=0;j<l;j++){
int xx=node[now].next[j],ll=node[now].length[j];
P x(dis[now]+ll,xx);calc.push(x);
if(dis[now]+ll<dis[xx]){dis[xx]=dis[now]+ll;pre[xx][i].clear();pre[xx][i].push_back(now);}
else if(dis[now]+ll==dis[xx])pre[xx][i].push_back(now);
}

然后我设计了一个递归函数来得到所有最短路中的边。但我并没有得到左右最短路,更像是得到了一棵由最短路组成的树,树根在终点d处。

void push(int now,int i,int ii){
pathsum=i>pathsum?i:pathsum;
path[i][ii].push_back(now);
int ll=pre[now][ii].size();
for(int j=0;j<ll;j++){
int last=pre[now][ii][j];
if(j)path[i+j][ii].push_back(now);
push(last,i+j,ii);
}
}

然后我再删掉这些边进行dijk寻路就可以了。

如何优化Dijkstra算法?

然而算法逻辑正确并不足够通过oj,目前的Dijkstra算法复杂度达到了惊人的O(n^2),tle也是必然的。
仔细思考之后,我想到每次寻找最近节点的步骤非常浪费时间:
原代码:

        int min=100000000,minj=0;
for(int j=0;j<n;j++){if(min>dis[j]&&!v[j]){min=dis[j],minj=j;}}

而我们完全可以将当前的距离写进一个优先队列中,每次在这个最小堆中取最小值就可以了。至于更新的时候,如果有更小的值可以直接加入,无需删去原来的值,毕竟我们始终只会用到最小的值。
优化后代码如下:

P x=calc.top();min=x.first;minj=x.second;calc.pop();
while(v[minj]){P x=calc.top();min=x.first;minj=x.second;calc.pop();}

(其实是一个dowhile结构)
然后复杂度就降低到lgn了

彩蛋:附著名oier的ac代码如下

#include<bits/stdc++.h>
using namespace std;
#define N 33333
#define M 833333
#define ll long long
#define PI pair<ll,int>
#define pb push_back
#define mp make_pair
priority_queue<PI,vector<PI>,greater<PI>>q;
int fir[N],l[M],to[M],w[M],ec=1,ban[M],S,T;
void add(int a,int b,int v){l[++ec]=fir[a];fir[a]=ec;w[ec]=v;to[ec]=b;}
int n,m,pre[N],sta[N],tp; ll d[N],D[N];
int inSP[N];
void dijk(ll*d,int S,int T,int o){
memset(d,0x3f,N<<3);
q.push(mp(d[S]=0,S));
while(q.size()){
int x=q.top().second;
ll D=q.top().first;
q.pop();
if(D^d[x])continue;
for(int i=fir[x],y;i;i=l[i]){
y=to[i];
if(d[y]>D+w[i]&&!ban[i])
q.push(mp(d[y]=D+w[i],y)),pre[y]=x;
}
}
if(o&&d[T]<1e16){
puts("start");
int p=T;
while(p^S) sta[++tp]=p=pre[p];
while(tp) printf("%d\n",sta[tp--]);
printf("%d\nend\n%lld\n",T,d[T]);
} }
int main(){
cin>>n>>m;
for(int i=0,a,b,W;i<m;++i)
scanf("%d%d%d",&a,&b,&W),add(a,b,W),add(b,a,W);
cin>>S>>T;
dijk(d,S,T,2);
dijk(D,T,S,0);
for(int i=0;i<n;++i)
inSP[i]=d[i]+D[i]==d[T];
for(int i=2,x,y;i<=ec;++i){
x=to[i^1];
y=to[i];
if(inSP[x]&&inSP[y]&&d[y]==d[x]+w[i])
ban[i]=ban[i^1]=1;
}
dijk(d,S,T,1);
}

单源最短路问题:OJ5——低德地图的更多相关文章

  1. dijkstra算法解决单源最短路问题

    简介 最近这段时间刚好做了最短路问题的算法报告,因此对dijkstra算法也有了更深的理解,下面和大家分享一下我的学习过程. 前言 呃呃呃,听起来也没那么难,其实,真的没那么难,只要弄清楚思路就很容易 ...

  2. Bellman-Ford算法解决单源最短路问题

    #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define max 100 #define I ...

  3. hdu 2544 单源最短路问题 dijkstra+堆优化模板

    最短路 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  4. 单源最短路问题--朴素Dijkstra & 堆优化Dijkstra

    许久没有写博客,更新一下~ Dijkstra两种典型写法 1. 朴素Dijkstra     时间复杂度O(N^2)       适用:稠密图(点较少,分布密集) #include <cstdi ...

  5. 单源最短路问题 Dijkstra 算法(朴素+堆)

    选择某一个点开始,每次去找这个点的最短边,然后再从这个开始不断迭代,更新距离. 代码: 朴素(vector存图) #include <iostream> #include <cstd ...

  6. bellman-ford 单源最短路问题 图解

    ​ 核心思想:松弛操作 对于边(u,v),用dist(u)和(u,v)的和尝试更新dist(v): dist(v) = min(dist(v) , dist(u)+l(u,v) 注:dist(i)为源 ...

  7. [ACM_图论] Domino Effect (POJ1135 Dijkstra算法 SSSP 单源最短路算法 中等 模板)

    Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...

  8. UVa 12661 (单源最短路) Funny Car Racing

    题意: 有一个赛车跑道,可以看做一个加权有向图.每个跑道(有向边)还有一个特点就是,会周期性地打开a秒,然后关闭b秒.只有在赛车进入一直到出来,该跑道一直处于打开状态,赛车才能通过. 开始时所有跑道处 ...

  9. 利用分支限界法求解单源最短路(Dijkstra)问题

    分支限界法定义:采用Best fist search算法,并使用剪枝函数的算法称为分支界限法. 分支限界法解释:按Best first的原则,有选择的在其child中进行扩展,从而舍弃不含有最优解的分 ...

随机推荐

  1. 学习heartbeat-01简介

    1.Heartbeat介绍 Heartbeat 是一个基于Linux开源的,被广泛使用的高可用集群系统,自1999年开始到现在,发布了众多版本,是目前开源Linux-HA项目最成功的一个例子,在行业内 ...

  2. Netty学习摘记 —— 再谈EventLoop 和线程模型

    本文参考 本篇文章是对<Netty In Action>一书第七章"EventLoop和线程模型"的学习摘记,主要内容为线程模型的概述.事件循环的概念和实现.任务调度和 ...

  3. Python - time标准库使用与程序计时

  4. C++ | 简单工厂模式 | 复数计算器

    简单工厂模式最直观的一个应用便是实现一个计算器的程序. 比如,公司让你给计算器添加一个幂运算的功能,你只需要设计一个幂运算的类,并实现幂运算的逻辑,然后让该类继承自运算类即可. 简单工厂模式: 简单工 ...

  5. 基于Node的React图片上传组件实现

    写在前面 红旗不倒,誓把JavaScript进行到底!今天介绍我的开源项目 Royal 里的图片上传组件的前后端实现原理(React + Node),花了一些时间,希望对你有所帮助. 前端实现 遵循R ...

  6. sql server总结二

    一些常用查询 Insert into 表(字段一,字段二,字段三,---) values(插入一,插入二,插入三,----) Update 表 set 字段= where 字段= Delete fro ...

  7. 关于webpack,你想知道的都在这;

    咱也标题党一回 哈哈哈 要使用webpack优化项目打包构建速度,首先得知道问题出在哪, 要知道问题出在哪,首先得知道webpack 打包的基本原理才能针对性的去做优化,下面首先了解webpack基本 ...

  8. vue过滤金额自动补全小数点

    watch:{ //监听input双向绑定 balance(value) { //保留2位小数点过滤器 不四舍五入 var toFixedNum = Number(value).toFixed(3); ...

  9. 猿人学python爬虫第一题

    打开网站.F12,开启devtools.发现有段代码阻止了我们调试 好的.接下来有几种解决方法 1- 绕过阻止调试方法 方法1(推荐) 鼠标放在debugger该行,左边数字行号那一列.右键选择不在永 ...

  10. Qt QPropertyAnimation+QTimer实现自制悬浮窗

    目录 Qt下的悬浮窗 QPropertyAnimation QTimer 事件过滤 图标变换 自适应窗口大小 使用方法 Qt下的悬浮窗 最近项目需要一个类似于360悬浮球类似的悬浮窗,当鼠标放入停留一 ...