第k短路 dijkstra+A* POJ2449 解题报告
题意:
输入格式:
n m
a1 b1 v1
a2 b2 v2
…
am bm vm
s t k
解释:给定n个点,m条边,每条边从 ai 到 bi ,权值为vi,求以s为起点,到达t的第k短路。注意:如果s==t,则s必须经过一条非空路径到达t。即最短路不能是0。
思路:
① 利用dijkstra计算出终点t到其他点 “逆着走” 的最短距离,并存储在数组 d 上。d[i]表示t到i的最短距离。
比如:s = 4,t = 1
那么d[2] = 1,d[3] = 2,这就是逆着走的意思。
② 进行A*搜索,从s出发,进行一个类似于BFS的搜索,每次选出“最短距离”的路径,直到到达点t k次时,说明选出了到达t的第k短路,则返回其路径长度。
最短距离的计算方式为:设当前点为p,则 s到达p的长度 加上 d[p] (t到p的最短路径)即为其比较基准。
比如:
- 从 4 出发 到 2 时的距离是2,那么“最短距离”应该说2+d[2] = 2+1 = 3
- 如果说路径是4->3->2->3->2,此时4到2的距离应该是4,那么最短距离应该就是4+d[2] = 4+1 = 5
下面是代码,注释可帮助理解
#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
//最大顶点数
const int maxn = 1009;
typedef long long int ll;
//dijkstra得出的"反向"最短距离
ll d[maxn];
//总顶点数
int n;
//Node 用于存储边,和供dijkstra算法使用
struct Node{
//to就是去哪个点,value就是权值
ll to,value;
void set(ll a,ll b){
to = a;
value = b;
}
//权值变相反数,这个函数是为了让最大堆变最小堆
void re(){
value*=-1;
}
};
//为了配合priority_queue而设置的函数
bool operator<(const Node &a,const Node &b){
return a.value<b.value;
}
vector<Node> pg[maxn];//正向图,存正向边
vector<Node> ng[maxn];//反向图,存反向边,供dijkstra使用
bool vis[maxn];//标记是否访问到了,dijkstra使用
//跑A*用的类
struct Side{
ll to,value;
void set(ll a,ll b){
to = a;
value = b;
}
void re(){
value*=-1;
}
};
//注意这里的比较函数发生了改变哦
bool operator<(const Side &a,const Side &b){
//A*用的函数
return a.value-d[a.to]<b.value-d[b.to];
}
void init(){
//-1标识未访问,其实也可以用INF
for(int i = 0;i<n;++i){
d[i] = -1;
}
}
//s是起点
void dijkstra(int s){
init();
Node work;
d[s] = 0;
work.set(s,0);
priority_queue<Node>q;
q.push(work);
while(!q.empty()){
work = q.top();
q.pop();
//其实这里用了re()没什么用,因为我没用到现在的work.value的实际值
//但是呢指不定我手贱使用了它,所以预防一下
work.re();
ll from = work.to;
if(vis[from])continue;
vis[from] = true;
int len = ng[from].size();
ll to,value;
for(int i = 0;i<len;++i){
to = ng[from][i].to;
value = ng[from][i].value;
if(d[from]+value<d[to]||d[to]==-1){
d[to] = d[from]+value;
//特别注意re(),取相反数,最大堆变最小堆
work.set(to,d[to]);
work.re();
q.push(work);
}
}
}
}
ll solve(int s,int t,int k){
//检查是否不可到达,否则可能在圈里无限跑,导致TLE
/*这是没了下面这一语句时的无限跑数据:
3 2
1 2 1
2 1 1
1 3 1
*/
if(d[s]==-1)return -1;
//根据题意,如果s==t,必须经过路径,不能原地到达
//因为这里0也是算最短路的,所以需要多跑一次
if(s==t)++k;
priority_queue<Side> q;
Side work;
work.set(s,0);
q.push(work);
while(!q.empty()){
work = q.top();
q.pop();
//这里的re()是有必要的
work.re();
//值必须先保存,否则work可能被set()更改
ll from = work.to,value = work.value;
if(from == t){
k--;
//第k次了,返回答案
if(k==0)return value+d[from];
}
int len = pg[from].size();
while(len--){
ll to = pg[from][len].to;
ll Add = pg[from][len].value;
work.set(to,value+Add);
//同样的注意re()
work.re();
q.push(work);
}
}
return -1;
}
int main(){
//freopen("in.txt","r",stdin);
ll m,from,to,s,t,k;
ll value;
Node work;
scanf("%lld %lld",&n,&m);
while(m--){
//注意它的点是1起始,我有点不习惯,所以转换成了0起始
scanf("%lld %lld %lld",&from,&to,&value);
work.set(from-1,value);
//加反向边
ng[to-1].push_back(work);
//加正向边
work.set(to-1,value);
pg[from-1].push_back(work);
}
scanf("%lld %lld %lld",&s,&t,&k);
dijkstra(t-1);
ll ans = solve(s-1,t-1,k);
printf("%lld\n",ans);
return 0;
}