本题就是一道单源最短路问题。由于是稀疏图,我们采用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. 【Azure Developer】使用PowerShell Where-Object方法过滤多维ArrayList时候,遇见的诡异问题 -- 当查找结果只有一个对象时,返回结果修改了对象结构,把多维变为一维

    问题描述 编写PowerShell脚本,以多维(3维)数组中第二维度的值进行过滤,并打印出结果 #三维数组源数据 "A", "11", "Cheng ...

  2. js技术之循环for

    案例:把所有单词以空格为分割并将首字母转为大写 <!DOCTYPE html><html lang="en"><head> <meta c ...

  3. 【C语言】预处理、宏定义、内联函数 _

    一.由源码到可执行程序的过程 1. 预处理: 源码经过预处理器的预处理变成预处理过的.i中间文件   1 gcc -E test.c -o test.i 2. 编译: 中间文件经过编译器编译形成.s的 ...

  4. 利用Matlab快速绘制栅格地图

    代码演示 % 基于栅格地图的机器人路径规划算法 % 第1节:利用Matlab快速绘制栅格地图 clc clear close all %% 构建颜色MAP图 cmap = [1 1 1; ... % ...

  5. 如何将Matlab中“模糊控制设计器”的隶属度函数导出图片(figure)

    如何将Matlab中"模糊控制设计器"的隶属度函数导出图片(figure)详情参考matlab官方帮助手册:plotmf()函数https://www.mathworks.com/ ...

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

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

  7. 手把手带你撸一把springsecurity框架源码中的认证流程

    提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...

  8. Spring-AOP动态代理技术(底层代码)

    1.JDK代理:基于接口的动态代理技术 目标对象必须有接口,目标对象有什么方法,目标接口就有什么方法, 运行期间基于接口动态生成代理对象,所以代理对象也就有目标对象同样的方法. 注意:以下代码只是底层 ...

  9. GEOS 使用记录

    GEOS 使用记录 官网 https://trac.osgeo.org/geos/ https://libgeos.org/ 下载地址 https://libgeos.org/usage/downlo ...

  10. Python学习报告及后续学习计划

    第一次有学习Python的想法是源于寒假在家的时候,高中同学问我是否学了Python(用于深度学习),当时就到b站收藏了黑马最新的教学视频,但是"收藏过等于我看了",后续就是过完年 ...