问题描述
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\n",Min);
}
return 0;
}