最短路径(shopth)(信息学奥赛一本通- P1378)
【输入】
第1行:一个正数n(2≤n≤80),表示图G的顶点总数。
第2行:一个整数,表示源点v0(v0∈V,v0可以是图G中任意一个顶点)。
第3至第n+2行,用一个邻接矩阵W给出了这个图。
【输出】
共包含n-1行,按照顶点编号从小到大的顺序,每行输出源点v0到一个顶点的最短距离。每行的具体格式参照样例。
【输入样例】
5
1
0 2 - - 10
- 0 3 - 7
- - 0 4 -
- - - 0 5
- - 6 - 0
【输出样例】
(1 -> 2) = 2
(1 -> 3) = 5
(1 -> 4) = 9
(1 -> 5) = 9
【提示】
样例所对应的图如下:

0. 题目概要
给定一个N个顶点的图(N<=80)和源点v0。图通过邻接矩阵给出,矩阵中包含整数权值(可能为负)和字符 -(表示无边)。要求计算源点到其他所有点的最短路径。
1. 核心思路
1.1 算法选择
虽然N很小,Floyd 或 Dijkstra 均可解。但考虑到题目含负权边且需处理单源最短路,SPFA 是一个性价比极高的选择。它既能处理负边,在稀疏图上的平均效率也优于 Bellman-Ford。
1.2 数据结构转化
-
输入:O(N^2)的邻接矩阵。
-
存储:为了优化 SPFA 的遍历效率,我们在读入时将矩阵转化为链式前向星(邻接表)。
-
转化逻辑:遍历矩阵
(i, j),若内容不是-且不是0(或根据题意忽略自环),则调用addedge(i, j, w)建边。
2. 重难点解析:字符串输入的“坑”
本题最大的难点在于输入数据的解析。
矩阵元素可能是:
-
正整数(如
10) -
负整数(如
-5) -
无边标记(
-)
难点:普通 cin >> int 会在读取到 - 字符时崩溃。必须使用 string 读取,然后分类讨论:
-
情况 A:正数
-
判定:
s[0] != '-' -
处理:遍历字符串,计算数值。
-
-
情况 B:无边
-
判定:
s[0] == '-'且s.size() == 1 -
处理:跳过,不建边。
-
-
情况 C:负数
-
判定:
s[0] == '-'且s.size() > 1 -
处理:从
s[1]开始遍历计算数值,最后结果取反。
-
3. 易错点警示
-
负号与减号混淆:
-
这是最容易 WA 的地方。必须严格区分代表“无边”的单个
-和代表“负数”的-前缀。 -
对策:使用
s.size()辅助判断。
-
-
SPFA 的状态复位:
-
出队时必须执行
is_used[u] = 0。这是 SPFA 与 Dijkstra 的本质区别(节点可能多次入队)。
-
-
无穷大的输出控制:
-
题目要求输出最短距离,如果不可达(
dis[i]==0x3f3f3f3f)
-
4. 完整代码
#include <iostream>
#include <queue>
using namespace std;
int n,v0;
int h[100];
int dis[100];
int vtex[10000];
int nxt[10000];
int wt[10000];
int idx;
queue<int> q;
bool is_used[150];//标记每个点是否在队中
void spfa(int s){
dis[s]=0;//源点到自己距离为0
q.push(s);//源点入队
is_used[s]=1;//标记源点已经入队
while(!q.empty()){
int tmp=q.front();//访问队首元素
q.pop();//队首出队
is_used[tmp]=0;//出队后解除标记
int p=h[tmp];//tmp的头指针
while(p!=-1){//访问所有p的邻接点
//如果邻接点到源点的距离大于tmp到源点的距离+边权 就更新
if(dis[vtex[p]]>dis[tmp]+wt[p]){
dis[vtex[p]]=dis[tmp]+wt[p];
//如果发生了更新且该邻接点不在队中就把邻接点入队
if(is_used[vtex[p]]==0){
q.push(vtex[p]);
is_used[vtex[p]]=1;//并把邻接点打上标记
}
}
p=nxt[p];//更新指针
}
}
}
void addedge(int u,int v,int w){
vtex[idx]=v;
nxt[idx]=h[u];
wt[idx]=w;
h[u]=idx++;
}
int main(){
cin>>n>>v0;
string a;
memset(h,-1,sizeof(h));//初始化邻接矩阵头指针数组
//建图,将邻接矩阵变成邻接表
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int w=0;
cin>>a;
if(a[0]!='-'){//如果有边且为正边
for(int k=0;k<a.size();k++){//字符串转换为数字
w=w*10+(a[k]-'0');
}
addedge(i,j,w);//有向图
}
//这里很容易出错,因为负数s[0]与没有边是一样的,所以很容易丢
else if(a[0]=='-'&&a.size()!=1){//如果有边且为负边
for(int k=1;k<a.size();k++){//字符串转换为数字
w=w*10+(a[k]-'0');
}
addedge(i,j,-w);//有向图 负边
}
}
}
/*//输出邻接表检查一下
for(int i=1;i<=n;i++){
cout<<i<<" :";
int p=h[i];
while(p!=-1){
cout<<vtex[p]<<" "<<wt[p]<<" ";
p=nxt[p];
}
cout<<endl;
}
*/
for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f;//初始化dis数组为无穷
spfa(v0);
for(int i=1;i<=n;i++){//输出所有点到源点的距离
//如果不是源点自身且有边存在
if(i!=v0 && dis[i]!=0x3f3f3f3f)
cout<<"("<<v0<<" -> "<<i<<")"<<" = "<<dis[i]<<endl;
}
return 0;
}
更多推荐


所有评论(0)