SSL1613 最短路径问题(Floyed&Dijkstra)


原题链接

SSL1613 外网进不去

题目大意

平面上有 n n n个点,每个点的坐标均在 − 10000 -10000 10000 10000 10000 10000之间。其中的一些点之间有连线。若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点直线的距离。现在的任务是找出从一点到另一点之间的最短路径。

输入格式

输入共有 n + m + 3 n+m+3 n+m+3行,其中:
第一行为一个整数 n n n
2 2 2行到第 n + 1 n+1 n+1行(共 n n n行),每行的两个整数 x x x y y y,描述一个点的坐标(以一个空格隔开)。
n + 2 n+2 n+2行为一个整数 m m m,表示图中的连线个数。
此后的 m m m行,每行描述一条连线,由两个整数 i , j i,j i,j组成,表示第 i i i个点和第 j j j个点之间有连线。
最后一行:两个整数 s s s t t t,分别表示源点和目标点。

输出格式

输出仅一行,一个实数(保留两位小数),表示从 s s s t t t的最短路径的长度。

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

5
0 0 
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

3.41

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
样例数据如图所示。
在这里插入图片描述
先从 1 1 1号点到 3 3 3号点,再到 5 5 5号点,总共路程长度为 2 + 2 ≈ 3.41 2+\sqrt{2} \approx 3.41 2+2 3.41

数据范围

对于 100 % 100\% 100%的数据满足: 1 ≤ n ≤ 100 1 \le n \le 100 1n100

解题思路

此题可以用三种方法来解。


F l o y e d − W a r s h a l l Floyed-Warshall FloyedWarshall做法
F l o y e d − W a r s h a l l Floyed-Warshall FloyedWarshall算法(以下简称 F l o y e d Floyed Floyed)其实就是一种贪心做法,枚举其中的一个点作为中转点,再判断是否为更优路径。
如上图,当我们求 1 1 1 5 5 5的距离时,我们可以枚举中转点,把原来的 1 1 1 5 5 5的距离 和后来加上中转点后 1 1 1 3 3 3的距离加上 3 3 3 5 5 5的距离 作比较,就可以求出更优距离。
大体的框架如下:

输入时建边
for(枚举中转点)
    for(枚举开始点)
        for(枚举结束点)
            dist[开始][结束]=max(dist[开始][结束],dist[开始][中转点]+dist[中转点][结束]);
输出

时间复杂度 Θ ( n 3 ) \Theta(n^3) Θ(n3)
核心代码:

for(int k=1; k<=n; k++)
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);

D i j k s t r a Dijkstra Dijkstra算法
D i j k s t r a Dijkstra Dijkstra算法是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。
核心思想:
从某一个点出发,依次寻找离他和已经经过的点最近的点,把他标记,此时他的值就是起点到当前点的最短路径。
让我们用图来尝试理解一下。
首先,造一个和样例一样的图。
d i s i dis_i disi i i i点到最近的蓝点的路径长度, v i s i vis_i visi代表 i i i点有没有标蓝, d i d_i di代表从出发点到 i i i点的最短路径长度。
0
初始时 d i s dis dis数组为 [ 0 , ∞ , ∞ , ∞ , ∞ ] [0,\infty,\infty,\infty,\infty] [0,,,,] v i s vis vis数组为 [ F , F , F , F , F ] [F,F,F,F,F] [F,F,F,F,F] d d d数组为 [ ∞ , ∞ , ∞ , ∞ , ∞ ] [\infty,\infty,\infty,\infty,\infty] [,,,,]

让我们把一号点作为出发点。
1
此时 d i s dis dis数组为 [ 0 , 2 , 2 2 , 2 , ∞ ] [0,2,2\sqrt2,2,\infty] [0,2,22 ,2,] v i s vis vis数组为 [ T , F , F , F , F ] [T,F,F,F,F] [T,F,F,F,F] d d d数组为 [ 0 , ∞ , ∞ , ∞ , ∞ ] [0,\infty,\infty,\infty,\infty] [0,,,,]

此时,离已经标蓝的点最近的点是 2 2 2号,长为 2 2 2,把 2 2 2号点标蓝, v i s 2 vis_2 vis2改为 T T T
2
此时 d i s dis dis数组为 [ 0 , 2 , 2 2 , 2 , 2 ] [0,2,2\sqrt2,2,\sqrt2] [0,2,22 ,2,2 ] v i s vis vis数组为 [ T , T , F , F , F ] [T,T,F,F,F] [T,T,F,F,F] d d d数组为 [ 0 , 2 , ∞ , ∞ , ∞ ] [0,2,\infty,\infty,\infty] [0,2,,,]

此时,离已经标蓝的点最近的点是 5 5 5号,长为 2 \sqrt2 2 ,把 5 5 5号点标蓝, v i s 5 vis_5 vis5改为 T T T
3
此时 d i s dis dis数组为 [ 0 , 2 , 2 , 2 , 2 ] [0,2,\sqrt2,2,\sqrt2] [0,2,2 ,2,2 ] v i s vis vis数组为 [ T , T , F , F , T ] [T,T,F,F,T] [T,T,F,F,T] d d d数组为 [ 0 , 2 , ∞ , ∞ , 2 + 2 ] [0,2,\infty,\infty,2+\sqrt2] [0,2,,,2+2 ]

此时,离已经标蓝的点最近的点是 3 3 3号,长为 2 \sqrt2 2 ,把 3 3 3号点标蓝, v i s 3 vis_3 vis3改为 T T T
4
此时 d i s dis dis数组为 [ 0 , 2 , 2 , 2 , 2 ] [0,2,\sqrt2,2,\sqrt2] [0,2,2 ,2,2 ] v i s vis vis数组为 [ T , T , T , F , T ] [T,T,T,F,T] [T,T,T,F,T] d d d数组为 [ 0 , 2 , 2 + 2 2 , ∞ , 2 + 2 ] [0,2,2+2\sqrt2,\infty,2+\sqrt2] [0,2,2+22 ,,2+2 ]

此时,离已经标蓝的点最近的点是 4 4 4号,长为 2 2 2,把 2 2 2号点标蓝, v i s 2 vis_2 vis2改为 T T T
5
此时 d i s dis dis数组为 [ 0 , 2 , 2 , 2 , 2 ] [0,2,\sqrt2,2,\sqrt2] [0,2,2 ,2,2 ] v i s vis vis数组为 [ T , T , T , T , T ] [T,T,T,T,T] [T,T,T,T,T] d d d数组为 [ 0 , 2 , 2 + 2 2 , 2 , 2 + 2 ] [0,2,2+2\sqrt2,2,2+\sqrt2] [0,2,2+22 ,2,2+2 ]

到此,算法结束。 d 5 d_5 d5就是从出发点到 5 5 5号点的距离,为 2 + 2 ≈ 3.41 2+\sqrt2\approx 3.41 2+2 3.41

时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)

核心代码:

for(int i=1; i<n; i++)
{
    double minn=0x7fffffff;
    int id;
    for(int j=1; j<=n; j++)
        if(!vis[j]&&dist[j]<minn)
            minn=dist[j],id=j;
    vis[id]=true;
    for(int j=0; j<road[id].size(); j++)
    {
        int temp=road[id][j];
        if(!vis[temp])
            dist[temp]=min(dist[temp],dist[id]+dis(point[id],point[temp]));
    }
}

注 意 : D i j k s t r a 不 能 处 理 存 在 负 边 权 的 情 况 ! \large{{\color{red}{注意:Dijkstra不能处理存在负边权的情况!}}} :Dijkstra


堆优化的 D i j k s t r a Dijkstra Dijkstra算法
上面的 D i j k s t r a Dijkstra Dijkstra算法的时间复杂度为 Θ ( n 2 ) \Theta(n^2) Θ(n2),还是非常耗时,如果用堆优化的话,时间复杂度可以降到 Θ ( n log ⁡ 2 n ) \Theta(n\log_2 n) Θ(nlog2n)
这里要用到的是优先队列:priority_queue
优先队列默认的是大根堆,即从大到小的一个堆,但是在这里我们要用的是把权值从小到大排序的小根堆,应该如何转化呢?
根据在数轴上一个数越大,他的相反数就越小的道理,即53的相反数为-5-3,但是大小从原来的5 > 3变成了-5 < -3
因此,我们只需要在权值上面取一个相反数,就可以把大根堆变成小根堆。
值得注意的是:在每一次一个点被加入了队列之后,有可能他已经被修改过了,但是由于他在队列里面的原因,这个新值并没有加入队列。因此,我们要在当前权值和当前答案不一致的情况下直接continue
时间复杂度 Θ ( ( n + m ) log ⁡ 2 n ) \Theta((n+m)\log_2n) Θ((n+m)log2n)
核心代码:

while(pq.size())
{
    pair<double,int> now=pq.top();
    pq.pop();
    double val=-now.first;
    int id=now.second;
    // vis[id]=true;
    if(dist[id]!=val)
        continue;
    for(int i=0; i<road[id].size(); i++)
    {
        int temp=road[id][i];
        if(dist[temp]>dist[id]+dis(point[id],point[temp]))
        {
            dist[temp]=dist[id]+dis(point[id],point[temp]);
            pq.push(make_pair(-dist[temp],temp));
        }
    }
}

最后,祝大家早日
请添加图片描述


上代码

C o d e   1 Code\ 1 Code 1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>

using namespace std;

typedef pair<double,double> state;

double      dist[110][110];
double      d[110][110];
state       point[110];
int         f,t;
int         n,m;

double dis(state x,state y)
{
    return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        double x,y;
        cin>>x>>y;
        point[i]=make_pair(x,y);
    }
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            dist[i][j]=0x3f3f3f3f;
    cin>>m;
    for(int i=1; i<=m; i++)
    {
        int x,y;
        cin>>x>>y;
        dist[x][y]=dist[y][x]=dis(point[x],point[y]);
    }
    cin>>f>>t;
    for(int k=1; k<=n; k++)
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
    printf("%0.2lf\n",dist[f][t]);
    // for(int i=1; i<=n; i++)
    // {
    //     for(int j=1; j<=n; j++)
    //         printf("%0.2lf ",dist[i][j]);
    //     cout<<endl;
    // }
    return 0;
}

C o d e   2 Code\ 2 Code 2

#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>

using namespace std;

typedef pair<double,double> state;

vector<int> road[110];
double      dist[110];
state       point[110];
bool        vis[110];
int         n,m,f,t;

double dis(state x,state y)
{
    return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        double x,y;
        cin>>x>>y;
        point[i]=make_pair(x,y);
    }
    cin>>m;
    for(int i=1; i<=m; i++)
    {
        int x,y;
        cin>>x>>y;
        road[x].push_back(y);
        road[y].push_back(x);
    }
    cin>>f>>t;
    for(int i=1; i<=n; i++)
        dist[i]=0x3f3f3f3f;
    dist[f]=0;
    int line_tot=0;
    for(int i=1; i<n; i++)
    {
        double minn=0x7fffffff;
        int id;
        for(int j=1; j<=n; j++)
            if(!vis[j]&&dist[j]<minn)
                minn=dist[j],id=j;
        vis[id]=true;
        for(int j=0; j<road[id].size(); j++)
        {
            int temp=road[id][j];
            if(!vis[temp])
                dist[temp]=min(dist[temp],dist[id]+dis(point[id],point[temp]));
        }
    }
    printf("%0.2lf\n",dist[t]);
    return 0;
}

C o d e   3 Code\ 3 Code 3

#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>

using namespace std;

typedef pair<double,double> state;

priority_queue<pair<double,int> >   pq;
vector<int>                         road[110];
double                              dist[110];
state                               point[110];
bool                                vis[110];
int                                 n,m,f,t;

double dis(state x,state y)
{
    return sqrt(pow(x.first-y.first,2.00)+pow(x.second-y.second,2.00));
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        double x,y;
        cin>>x>>y;
        point[i]=make_pair(x,y);
    }
    cin>>m;
    for(int i=1; i<=m; i++)
    {
        int x,y;
        cin>>x>>y;
        road[x].push_back(y);
        road[y].push_back(x);
    }
    cin>>f>>t;
    for(int i=1; i<=n; i++)
        dist[i]=0x7fffffff;
    dist[f]=0;
    pq.push(make_pair(0,f));
    while(pq.size())
    {
        pair<double,int> now=pq.top();
        pq.pop();
        double val=-now.first;
        int id=now.second;
        // vis[id]=true;
        if(dist[id]!=val)
            continue;
        for(int i=0; i<road[id].size(); i++)
        {
            int temp=road[id][i];
            if(dist[temp]>dist[id]+dis(point[id],point[temp]))
            {
                dist[temp]=dist[id]+dis(point[id],point[temp]);
                pq.push(make_pair(-dist[temp],temp));
            }
        }
    }
    printf("%0.2lf\n",dist[t]);
    return 0;
}

完美切题 ∼ \sim

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值