本题就是一道单源最短路问题。由于是稀疏图,我们采用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. ES6技术之“判空方案”

    一.常用方案 直接判断 代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...

  2. H5进阶篇--实现微信摇一摇功能

    在HTML5中,DeviceOrientation特性所提供的DeviceMotion事件封装了设备的运动传感器时间,通过改时间可以获取设备的运动状态.加速度等数据(另还有deviceOrientat ...

  3. 小程序web开发框架-weweb介绍

    weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web单面应用.如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中.在小程序大行其道的今天,它可以让你的小程序代码得到最大 ...

  4. JS中Map和ForEach的区别

    定义 forEach()方法: 针对每一个元素执行提供的函数. map()方法: 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来. 区别 forEach()方法不会返回执行 ...

  5. ansible模块解析及使用

    模块一:setup(收集远程主机信息) [root@zabbix30 /]# ansible test -m setup 模块二:ping(测试主机是否在线) [root@zabbix30 /]# a ...

  6. linux find命令 -mtime参数 根据修改时间查找文件

    命令:find 搜索路径 -mtime n 主要说明n的含义: 例: n=5 "5"指的是前 5~6 天那一天修改的文件 n=-5 "-5"指的是 5 天内修改 ...

  7. JavaScript 数学 (Math) 方法

    一.Math 方法 1.Math.round(x) 的返回值是 x 四舍五入为最接近的整数: Math.round(7.8); // 返回 8 Math.round(3.3); // 返回 3 2.M ...

  8. mmdetection 绘制PR曲线

    参考:https://github.com/xuhuasheng/mmdetection_plot_pr_curve 适用于COCO数据集 import os import mmcv import n ...

  9. 【第五课】VIM编辑器(学习笔记)

    4月10日学习笔记打卡

  10. vue-mobile-template 前端开源框架

    开源项目的由来 某天因公司业务需求,想寻找一款H5移动端的模板进行二次开发.但搜遍整个HUB都没法找到合适的空模板进行二次开发,所以心血来潮,于是有了 vue-mobile-template . 介绍 ...