HAOI 2005 路由选择问题 (最短路+次短路)

问题描述

X城有一个含有N个节点的通信网络,在通信中,我们往往关心信息从一个节点I传输到节点J的最短路径。遗憾的是,由于种种原因,线路中总有一些节点会出故障,因此在传输中要避开故障节点。
任务一:在己知故障节点的情况下,求避开这些故障节点,从节点I到节点J的最短路径S0。
任务二:在不考虑故障节点的情况下,求从节点I到节点J的最短路径S1、第二最短路径S2。

输入文件

第1行: N I J (节点个数 起始节点 目标节点)
第2—N+1行: Sk1 Sk2…SkN (节点K到节点J的距离为SkJ K=1,2,……,N)
最后一行: P T1 T2……Tp (故障节点的个数及编号)

输出文件

S0 S1 S2 (S1<=S2 从节点I到节点J至少有两条不同路径)

样例输入

5 1 5
0 10 5 0 0
10 0 0 6 20
5 0 0 30 35
0 6 30 0 6
0 20 35 6 0
1 2

样例输出
40 22 30

约束条件

(1)N<=50 N个节点的编号为1,2,…,N
(2)Skj为整数,Skj<=100,(K,J=1,2…,N 若Skj=0表示节点K到节点J没线路)
(3)P<=5  

思路:

首先简单的说一下次短路经:

次短路径可以看作是k短路径问题的一种特殊情况,求k短路径有Yen算法等较为复杂的方法,对于次短路径,可以有更为简易的方法。下面介绍一种求两个顶点之间次短路径的解法。

我们要对一个有向赋权图(无向图每条边可以看作两条相反的有向边)的顶点S到T之间求次短路径,首先应求出S的单源最短路径。遍历有向图,标记出可以在最短路径上的边,加入集合K。然后枚举删除集合K中每条边,求从S到T的最短路径,记录每次求出的路径长度值,其最小值就是次短路径的长度。

在这里我们以为次短路径长度可以等于最短路径长度,如果想等,也可以看作是从S到T有不止一条最短路径。如果我们规定求从S到T大于最短路径长度的次短路径,则答案就是每次删边后大于原最短路径的S到T的最短路径长度的最小值。

用Dijkstra+堆求单源最短路径,则每次求最短路径时间复杂度为O(Nlog(N+M) + M),所以总的时间复杂度为O(NM*log(N+M) + M^2)。该估计是较为悲观的,因为一般来说,在最短路径上的边的条数要远远小于M,所以实际效果要比预想的好。

对于前面两个询问,直接spfa即可(同时记录最短路径),对于第三个询问,每一次删掉最短路径中的一条边,然后再spfa找到删过边后的最短路,即为次短路。

代码:

#include<stdio.h>
#include<iostream>
#include<queue>
#include<string.h>
const int INF=0x3f3f3f3f;
using namespace std;
int n,st,ed,cnt,st1,ed1;
struct Node
{
    int to,val;
    int Next;
}node[2600];
int head[55];//头结点
int pro[55];//有问题的点
int pre[55];//前去节点
int dis[55];//距离
int vis[55];//标记有没有访问过
void init()
{
    for(int i=0;i<=n;i++)
        head[i]=-1;
}
void add(int a,int b,int w)
{
    node[cnt].to=b;
    node[cnt].val=w;
    node[cnt].Next=head[a];
    head[a]=cnt;
    cnt++;
}
void spfa(int flag)
{
    if(flag==1)
        memset(pre,0,sizeof(pre));
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        dis[i]=INF;
        vis[i]=0;
    }
    dis[st]=0;
    vis[st]=1;
    pre[st]=-1;
    q.push(st);

    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=node[i].Next)
        {
            int y=node[i].to;
            if ((x == st1 && y == ed1) || (x == ed1 && y == st1)) continue;
            if(pro[y]==1) continue;//第一次找最短路的时候遇见故障点要跳过
            if(dis[y]>dis[x]+node[i].val)
            {
                dis[y]=dis[x]+node[i].val;
                if(flag==1)  pre[y]=x;
                if(vis[y]==0)
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
}
int main()
{
    while(scanf("%d%d%d",&n,&st,&ed))
    {
        init();
        cnt=0;
        int w,num;
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
            {
                scanf("%d",&w);
                if(w!=0)
                {
                    add(i,j,w);//按照头插法保存下图的信息
                }
            }
        scanf("%d",&w);
        for(int i=1;i<=w;i++)//将有问题的点标记下来
        {
            scanf("%d",&num);
            pro[num]=1;
        }

        //去掉有故障的点寻找最短路
        spfa(1);
        printf("%d ",dis[ed]);

        //不考虑有故障的点寻找最短路
        memset(pro,0,sizeof(pro));
        spfa(1);
        printf("%d ",dis[ed]);

        //求次短路是在不考虑有故障的点的基础的
        int Min=INF;
        int v ;
        v=ed;//倒着一条边一条边的删除
        while(pre[v]!= -1)
        {
            st1=pre[v];//删除的这条边的起始点
            ed1=v;//删除的这条边的终点
            spfa(0);
            Min=min(Min,dis[ed]);//然后在这些最短路里面找一条最小的
            v=pre[v];
        }
         printf("%d
",Min);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/cmmdc/p/7725423.html