POJ 3255 Roadblocks (次短路问题)

本文介绍两种求解次短路径的方法,一种是通过枚举每条边并使用SPFA算法,另一种是利用Dijkstra算法优化求解。文章详细解释了算法原理及实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://poj.org/problem?id=3255

题目大意:在一个图上有许多个农场,有个人从1农场出发,到他的朋友n农场去,他不想走一条最短路径,这次他想换条路走,要你帮他找一条次短路径,次短路的定义是,比最短路径长度短(可能有多条),但是不会比其他的路径长度长。而且告诉你数据中一定存在至少一条次短路。

常规方法:删除某一条边求最短路径,找出的最短路径比最短路径长,但是比其他路径短的结果就是次短路了。但是分析一下这样做的时间复杂度是,用spfa的时间复杂度大概是O(KE),E为边的总数=200000,然后枚举边O(E),那么总的时间复杂度就是O(KE^2),这样肯定就会超时了。

首先看看思路:最短路明显不会是次短路,因为题目说了次短一定是存在的,那么他们不可能重合,这样次短路肯定是最短路中某一条边不走,而走了其他边再回到最短路上,而且不可能绕两个地方,只可能绕一个地方,因为明显绕两个地方比绕一个地方的路径长,明显不是次短路了。

方法一:
我们可以枚举每条边s到t。然后用d[s]记录源点到s的最短距离,而用dr[t]记录t到汇点的最短距离,这样就只需要从t到s求一次最短路得到了len从s到t表示边s到t的这条边的长度。
然后我们枚举每一条边有:tmp=d[s]+dr[t]+len从s到t
找出其中比最短路小但是比其他路长的一个值就是次短路径了。

#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
#define maxv 5010
#define maxe 200100
#define INF 0xffffff
struct node{
    int v,w,next;
}edge[maxe];

int d[maxv],dr[maxv];
int n,m,cnt;
int head[maxv];
bool vis[maxv];

void add(int u,int v,int w)            //数据处理 
{
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

void spfa(int x,int dt[])             //SPFA找最短路 
{
    int i,v,u;
    queue<int>q;
    memset(vis,0,sizeof(vis));
    dt[x]=0;
    vis[x]=1;
    q.push(x); 
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;                                   //出栈之后标记为0 
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v;
            if(dt[v]>dt[u]+edge[i].w)
            {               
                dt[v]=dt[u]+edge[i].w;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}

int main()
{
    int a,b,c;
    int ans,tmp;
    while(cin>>n>>m)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=1;i<=n;i++)               
          d[i]=dr[i]=INF;
        while(m--)
        {
            cin>>a>>b>>c;
            add(a,b,c);
            add(b,a,c); 
        }
        spfa(1,d);                       //以1为起点做单源最短路 ,数据保存到d数组里  
        spfa(n,dr);                      //以n为起点做单源最短路,数据保存到dr数组里 
        ans=INF;
        for(int i=1;i<=n;i++)
        {
            for(int j=head[i];j!=-1;j=edge[j].next)
            {
                b=edge[j].v;
                c=edge[j].w;
                tmp=d[i]+dr[b]+c;              //d[i]为源点到i得最短路,dr[b]为汇点到b的最短路,c为len<i,b>即从i到b的最短路 
                if(tmp>d[n]&&ans>tmp)          //找到比最短路要长但是比其他路径都要短的,即是次短路 
                  ans=tmp;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
} 

方法二:
先来谈谈Dijkstra的优化。对于每次寻找到当前为访问过的点中距离最短的那一个,运用优先队列进行优化,避免全部扫描,每更新一个点的最短距离就加入优先队列。有人会问,一个点如果已经处理完成了,那它还留在队列中怎么办?我们放入队列时将一个点那时的顶点编号和最短距离进行打包,如果取出该点时,它当前的最短距离小于该点标记的最短距离,说明该点已经取到最短距离,不进行操作。或者直接用一个vis数组来记录某一个点是否已经取到最短距离;其次的优化是用邻接表存储与每一个点相连的所有边,方便处理。

而这道题的做法和最短路径基本一致,唯一的不同点在于,在求出最短路径的情况下必须要保留下次短路径。对于Dijkstra判断中取出的每一个点,如果到它的最短距离大于当前该点的次短距离,则当前该点已经取到最短距离和次短距离,不进行操作,否则进行两次判断:如果小于最短边,则赋给最短变,并将最短边赋给次短边;或者如果大于最短变且小于次短边,则赋给次短边。两次完成之后均要加入队列。要注意几点:

(1)由于是一张无向图,读入的时候必须正向、逆向分为两条边存储,所以实际有向边的数量为r的两倍,数组绝对不能开小!

(2)初始化时,源点的短边初始化为0,源点的次短边必须初始化为INF,而不是0。比如下面这组数据:

    4 2
    1 2 100
    2 4 200

答案应该是500,然而如果初始化为0则答案会输出700。因为500的结果是又1到2,在从2返回1,再到2,再到4,100+100+100+200=500得到的;如果次短边初始化为0,则次短路径不再返回源点,而是在2与4之间折返,会偏大。

(3)53行绝对不能直接赋值,而是要swap!因为最短边被修改后,它的值是要留给次短边的。

代码如下:

#include<iostream>
#include<queue>
#include<cstring> 
using namespace std;
const int INF=0x7fffffff;
const int maxn=100000+10;
struct node{
    int num,len;
    bool operator < (const node &a) const
    {
        return len>a.len;                            //优先队列强制设置为以len为关键词的小顶堆 
    }
};

int u[maxn*2],v[maxn*2],w[maxn*2];                 //依次表示每条道路上的起始点、终点和长度 
int dis[maxn/2],second[maxn/2];                    //记录通往每一个路口的最短和次短距离 
int first[maxn/2],next[maxn*2];
int n,m;

void Dijkstra()
{
    priority_queue<node>q;
    for(int i=1;i<n;i++)
      dis[i]=second[i]=INF;
    dis[0]=0;                                   //源点的最短边可以初始化为0 
    second[0]=INF;                              //但是源点的次短边必须初始化为INF,而不是0(否则会出错) 
    node temp;
    temp.num=temp.len=0;
    q.push(temp);
    while(!q.empty())
    {
        node head=q.top();
        q.pop();
        if(head.len>second[head.num])  //对于该点的最短距离大于当前该点的次短距离,则当前该点已经取到最短和次短距离,不进行操作 
          continue;
        int k=first[head.num];
        while(k!=-1)        
        {
            int d=head.len+w[k];           //否则进行两次判断 
            if(dis[v[k]]>d)                //判断一:如果小于最短距离(把最短距离赋给次短距离,再把当前距离赋给最短距离) 
            {
                swap(dis[v[k]],d);         //实际上灵活运用下面一个if判断,只要把当前距离跟最短距离进行交换即可 
                temp.len=dis[v[k]];
                temp.num=v[k];
                q.push(temp);
            }
            if(dis[v[k]]<d&&second[v[k]]>d)    //判断二:如果大于最短距离并小于次短距离 (把当前距离赋给次短距离) 
            {
                second[v[k]]=d;
                temp.len=second[v[k]];
                temp.num=v[k];
                q.push(temp);
            }
            k=next[k];                   //枚举每一条边 
        }
    }
}

int main()
{
    while(cin>>n>>m)
    {
        memset(first,-1,sizeof(first));
        for(int i=0;i<m;i++)
        {
            cin>>u[i]>>v[i]>>w[i];
            u[i]--;
            v[i]--;
            next[i]=first[u[i]];
            first[u[i]]=i;
            v[i+m]=u[i];
            u[i+m]=v[i];
            w[i+m]=w[i];
            next[i+m]=first[u[i+m]];
            first[u[i+m]]=i+m;
        }
        Dijkstra();
        cout<<second[n-1]<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值