最短路问题

  1何为最短路

  最短路算是OI上的经典问题了,最简单的问题为:在一个图上,求一个点到其他点的最短距离,即单源最短路。较为复杂的有求所有点到除它以外的其他点的最短路,即多源最短路。最短路的算法有很多,如dijkstra,floyd,bellman-ford,SPFA等,由于一些原因,除了判负环以外,SPFA基本会被卡住,因此在本文就不赘述了,本文主要讲dijkstra和floyd。

  2.1dijkstra原理概述

  dijkstra是对一个无负权边求单源最短路的算法,它是基于一个贪心的算法:设点A是距离原点O最近的未考虑的点,那么,设点B是另外一个未考虑的点,那么由于O点到B点的距离大于O点到A点的距离,所以即使点A到点B的距离为0,也是直接走O->A比O->B->A短。因此,我们求出A,再用A改变其他点即可。

  2.2代码实现

  首先看这道题:【模板】单源最短路径(弱化版)

  这道题中,我们注意到n的值较小,可以用n2 的时间复杂度的算法求解,由于m的大小,我们用链式前向星记录

#include <bits/stdc++.h>
using namespace std;
int k,i,j,ne[500001],to[500001],he[500001],l[500001],cnt,min1,n,m,s,d[100001],x,y,z;
bool b[100001];
int main()
{
  cnt=0;
  cin>>n>>m>>s;
  for (i=1; i<=n; i++)
  {
    b[i]=true;
    d[i]=2147483647;//初始化,d数组记录点i到点s的距离,b数组记录该点是否被访问过
  }
  for (i=1; i<=m; i++)
  {
    cin>>x>>y>>z;
    cnt++;
    ne[cnt]=he[x];
    to[cnt]=y;
    l[cnt]=z;
    he[x]=cnt;//链式前向星储存边
  }
  d[s]=0;
  d[0]=2147483647;
  for (i=2; i<=n; i++)//因为最后一点无需改变,所以只需n-1遍
  {
    min1=0;
    for (j=1; j<=n; j++)
      if (b[j]&&(d[j]<=d[min1])) min1=j;//求出最近点
    k=he[min1];
    b[min1]=false;
    while (k!=0)
    {
      d[to[k]]=min(d[to[k]],d[min1]+l[k]);//修改其他点
      k=ne[k];
    }
  }
  for (i=1; i<=n; i++)
    cout<<d[i]<<' ';
  return 0;
}

  这样,这道题就做完了。

  接下来,我们看这道题:【模板】单源最短路径(标准版)

  在这道题中,n到了十万,显然,n2的时间复杂度太大了,考虑一下该算法的优化,我们原本用for来求最近距离,如果用堆的话,不就到nlogn了吗?这道题就能做出来了:

#include<bits/stdc++.h>
using namespace std;
priority_queue<pair<long long,long long> >q;//虽然是大根堆,但是第一维取负后就是小根堆了
long long n,m,s,i,cnt,x,y,z,he[100001],ne[200001],l[200001],to[200001],d[100001];
bool b[100001];
int main()
{
    scanf("%lld%lld%lld",&n,&m,&s);
    for (i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld",&x,&y,&z);
        cnt++;
        ne[cnt]=he[x];
        to[cnt]=y;
        l[cnt]=z;
        he[x]=cnt;
    }
    memset(d,100000007,sizeof(d));
    d[s]=0;
    q.push(make_pair(0,s));//压入s
    while (q.size())
    {
        x=q.top().second;//取出最近点
        q.pop();
        if (b[x]) continue;
        b[x]=true;//记录已找
        for (i=he[x];i!=0 ;i=ne[i])
            if (d[to[i]]>d[x]+l[i])
            {
                d[to[i]]=d[x]+l[i];
                q.push(make_pair(-d[to[i]],to[i]));//压入堆
            }
    }
    for (i=1;i<=n;i++)
    printf("%lld ",d[i]);
    return 0;
}

  这样,dijkstra就告一段落了,若读者想要做增强的应用题,我找出以下几道题:

  小鸟的点心,该题题解

  路径统计,该题题解

  密室,该题题解

  抓住czx,该题题解

  (博主推销自己题解,哈哈,该博客只用来写洛谷题题解,欢迎参观

  3.1floyd原理概述

  floyd是一种求多源最短路的算法,设i,j是我们要求的两点,而k是两点中的一点且与i,j联通,那么i->j的距离就是min(i->j,i->k->j)枚举i,j,k,时间复杂度为n3

  3.2代码实现

  由于一下没找到例题,博主自己弄了一道:【模板】floyd

  这是一道裸的floyd模板题,先看核心代码:

    for (k=1;k<=n;k++)//k必须在最外面
        for (i=1;i<=n;i++)
            for (j=1;j<=n;j++)//i,j随便
            p[i][j]=min(p[i][j],p[i][k]+p[k][j]);//p[i][j]代表i到j的最短距离

  由于时间复杂度较大,floyd主要求多源的问题,而且也基本无法优化了,不多说了,这是完整代码:

#include<bits/stdc++.h>
using namespace std;
long long n,m,p[501][501],i,j,k,ans,x,y,l;
int main()
{
    scanf("%lld%lld",&n,&m);
    for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
        p[i][j]=1e17;//初始化
    for (i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld",&x,&y,&l);
        p[x][y]=min(p[x][y],l);//可能有重边,取最小值
        p[y][x]=p[x][y];
    }
    for (k=1;k<=n;k++)
        for (i=1;i<=n;i++)
            for (j=1;j<=n;j++)
            p[i][j]=min(p[i][j],p[i][k]+p[k][j]);
    for (i=1;i<=n;i++)
    {
        ans=0;
        for (j=1;j<=n;j++)
            if (j!=i) 
                ans=(ans+p[i][j])%998244354;//嘿嘿,不是998244353哦
        printf("%lld\n",ans);
    }
    return 0;
 } 

  本章就结束了,谢谢!

posted @ 2019-08-01 13:10  冰逝  阅读(745)  评论(2编辑  收藏  举报