「POJ2449」第 K 短路 题解报告
1 题面
给定一张N个点,M条边的有向图,求从S到T的第K短路的长度,路径允许重复经过点或边。
输入
第一行包含两个整数N和M (1 <= N <= 1000, 0 <= M <= 100000)。车站编号从1到N,每M条线路包含3个整数A、B、T (1 <= A, B <= N, 1 <= T <= 100)。表明在时间为T时,从第A站到第B站存在一条有向边。
最后一行由三个整数S, T和K (1 <= S, T <= N, 1 <= K <= 1000)组成。
输出
由单个整数组成的一行:使用第k条最短路径欢迎Uyuw公主的长度(所需时间)。如果第k条最短路径不存在,则输出“-1”(不带引号)。
样例
样例输入1
2 2
1 2 5
2 1 4
1 2 2
样例输出1
14
2 分析题面
2.1 题目简述
求s点到t点的第k短路径
2.2 思路
朴素算法:
用Dijkstra反复执行,t点在堆中第k次取出的结果
优化:
启发式搜索(HEURISTIC SEARCH)A*算法
Djikstra 的h(估价函数)版本
为了对未来产生的代价进行估计,设计一个估价函数,
能够预估任意状态到目标状态的
未来所需代价的估计值
这个估计值就需要用估价函数来计算
那估价函数怎么设计呢?
根据A*估价函数的设计原则
x-tT的估计距离应当不大于x->t的实际距离
所以我们可以这样做:
先用dijkstra求出每个点到t的最短距离,作为估价函数
这样既符合题意,又方便求解
不过现在堆里面存的东西不太一样,
现在堆中存的时从起点到当前节点已经花费的代价+从当前节点到终点的最短路径
3 代码实现(注释)
3.1定义
图的几个数组(结构体也可)以及另外两个结构体,分别供dij和A*用(结构体内要写重载运算符来实现优先队列)
3.2 输入
由于dijkstra是算的从t(终点)到各点的距离,所以存边的时候也要倒着存
然而A*是正着跑的,所以要存一正一反两次图
scanf("%d%d",&n,&m);//n个点,m条边
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x[i],&y[i],&z[i]);
add(y[i],x[i],z[i]);//存反向图
}
scanf("%d%d%d",&s,&t,&k);//起点s,终点t,K短路
dij();//得出"反向"最短距离
cnt=0;
memset(fir,0,sizeof(fir));//初始化
for(int i=1;i<=m;i++){
add(x[i],y[i],z[i]);//存原图
}
3.3 计算
易错点
if(s==t) k++;//起点和终点相同时,k要++
//因为如果s==t,0这条路不能算在K短路中,所以需要求K+1短路
小优化(A*里的)
(本来想写堆的,但是大家都会就没必要写了)
if(tot[u.id]>k) continue;
//如果当前想拓展的点cnt>k就没必要拓展了
//因为这个点已经是求到k+1短路了
//从这个点继续往下搜肯定得到的是大于等于k+1短路的路径
3. 4 输出
直接输出A*跑出来的结果即可
printf("%d",A());//输出
3.5 总体代码
#include<bits/stdc++.h>//万能头
using namespace std;
const int N=2*100002;
const int INF=2147483647;//尽可能大
int w[N],cnt,to[N],tot[N],nxt[N],fir[N],dis[N],x[N],y[N],z[N];
//dis:终点到各点的距离 x/y/z:建边两次用
// w/to/fir/nxt:图 tot:出队次数 cnt:建边用
bool vis[N];//标记
int n,m,t,s,k;
struct node {
int d,id;
//结构体初始化函数
node(int td,int tid){d=td;id=tid;} //结构体初始化函数
//重载运算符
bool operator < (const node& other) const{
return d > other.d;
}
};
//node供dijkstra算法使用
struct edge{
int id,h,g;//g:s到当前点的距离 h:t到当前点的距离
//id表示当前点(的排号or标号)
edge(int a,int b,int c){id=a;h=b;g=c;} //结构体初始化函数
//重载运算符
bool operator < (const edge& other) const{
return h+g > other.g+other.h;
} //优先选择f值小的点 A*算法精髓所在
};
void add(int x,int y,int z){
w[++cnt]=z;//权值
to[cnt]=y;//下个节点
nxt[cnt]=fir[x];//下条边的位置
fir[x]=cnt;//记录标号
}//邻接表建边
void dij(){
//dij算法求终点到各点的距离 用于估价函数h
//利用反向边确定估价函数h(x)
memset(vis,0,sizeof(vis));//初始化
for(int i=1;i<=n;i++){
dis[i]=INF;
}//初始化
dis[t]=0;//初始化t到t的距离为0
priority_queue<node> Q;//优先队列
//小根堆
Q.push(node(0,t));//d = 0 ,id =s 入队
while(!Q.empty()){
node u=Q.top();
Q.pop();
//若某个点已经被更新到最优,就不用再次更新其他点
if(vis[u.id]) continue;
vis[u.id]=1;
//从u.id 开始遍历边
for(int i=fir[u.id];i;i=nxt[i]){//松弛
int v=to[i],val=w[i];
if(u.d+val<dis[v]){
dis[v]=u.d+val;
//d = dis[v],id =v 入队
Q.push(node(dis[v],v));
}
}
}
}//Dijkstra堆优化版
int A(){//A*求第k短路
//启发性搜索决定了先后搜索出来的是:最短、次短、第三短...第k短
priority_queue<edge> Q;
//优先队列 优先选择f值小的点 跑正向图
Q.push(edge(s,0,dis[s]));
//id=s,h=0,g=dis[s]
while(!Q.empty()){
edge u=Q.top();//当前节点
Q.pop();//每次取估价函数值最小的节点
tot[u.id]++;//计算出队次数
if(tot[u.id]==k&&u.id==t) return u.h+u.g;
//找到第K短路 返回
//第几次出队,出队时的距离就是最几短距离
if(tot[u.id]>k) continue;//cnt>k没必要拓展
for(int i=fir[u.id];i;i=nxt[i]){//相连的点入队列
int v=to[i];
Q.push(edge(v,u.h+w[i],dis[v]));
//id=v,h=u.h+w[i],g=dis[v]
}
}
return -1;//不存在第k短路 输出-1
}
int main(){
scanf("%d%d",&n,&m);//n个点,m条边
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x[i],&y[i],&z[i]);//输入
add(y[i],x[i],z[i]);//原
}
scanf("%d%d%d",&s,&t,&k);//起点s,终点t,K短路
if(s==t) k++;//起点和终点相同,k++
dij();//dijkstra"反向"最短距离
cnt=0;//清0
memset(fir,0,sizeof(fir));//初始化
for(int i=1;i<=m;i++){
add(x[i],y[i],z[i]);//反向图
}
printf("%d",A());//输出
return 0;
}
//最短路跑spfa也行
4 总结
- 本题就是A*的版子题,对于A *的h(x)估计函数设计应该会有一个大概的理解。
- 在A*中的估计函数中如果要跑最短路就要从终点跑。。。(建边时也要存反向图)
- 相似题目: [SDOI2010] 魔法猪学院 / [SCOI2007]k短路
本文作者:Yvette的博客
本文链接:https://www.cnblogs.com/yvette1217/p/16132139.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步