本题就是一道单源最短路问题。由于是稀疏图,我们采用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. JAVA中内存分配的问题

    JAVA中内存分配的问题 1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构.说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出 ...

  2. css边距重叠的解决方案

    ** css防止边距重叠的方法 ** 今天整理了一下用css防止边距重叠的几种方法先假设一组dom结构 <div class="parent"> <div cla ...

  3. 前端每日实战:116# 视频演示如何用 CSS 和原生 JS 开发一个监控网络连接状态的页面

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/oPjWvw 可交互视频 此视频是可 ...

  4. 手把手教你学vue-4(vuex)

    1.首先明白vuex是做什么用的. 管理统一组件状态state.每个应用将仅仅包含一个 store 实例.单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态 ...

  5. linux系统引导过程

    linux系统引导过程 linux-0.11引导时,将依次运行BIOS程序.bootsect.s.setup.s和head.s,完成引导过程后进入到main函数运行.BIOS完成硬件的检查与初始化等工 ...

  6. mybatisPlus crud操作注意事项

    1.调用IService里的update方法,如果是自定义根据除主键外其它字段更新的时候,如果给主键id设置其它值不会更新主键id,如果未设置主键id值或者设置为null,同样不会更新主键id. 2. ...

  7. Java学习day7

    Java继承不同与c++,格式为: public class 子类名 extends 父类名{ 语句体; } 继承提高了代码的复用性与维护性 在子类方法中访问一个变量时,首先在子类局部范围查找,其次到 ...

  8. eNSP路由器启动#号问题排查

    1.删除拖出来的设备,重新拖出来一台---我用过[有时候好使] 2.确保Ensp的设置-工具-Virtual Box安装目录是否正确--我也遇到过[尤其是卸载掉Virtual Box重装之后] 3.确 ...

  9. PuddingSwap联合 ESBridge举办愚人节“币圈愚话”联合空投活动,完成任务即可获得惊喜奖励

    据官方消息,4月1日0:00- 4月2日23:59,PuddingSwap联合 ESBridge举办"币圈愚话"空投活动,完成任务即可获得惊喜奖励. 此次PuddingSwap联合 ...

  10. python基础练习题(题目 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少)

    day13 --------------------------------------------------------------- 实例021:猴子偷桃 题目 猴子吃桃问题:猴子第一天摘下若干 ...