题意:
给定一张N个点,M条边的有向图,求从起点到终点T的第K短路的长度。
思路:
求第K短路,我们先回忆一下Dijkstra求最短路的方法。
每次松弛了一个节点,则将该节点放入优先队列,然后在取优先队列的第一个点,即源点到该点距离最短的点,再用该点去松弛其他的点。最终求出的距离就是单源最短距离。
换句话说,就是用优先队列bfs对整张图进行遍历,当一个点被第一次取出时,此时的距离便是该点的最短距离。
由此,我们便得知了求第K短路的方法。即对于任意顶点 i ,当顶点 i 被第K次取出时,此时的距离便是该点的第K短路。
但是,如果直接用优先队列的bfs去求第K短路,无疑会造成大量的冗余搜索。因此我们需要用A*算法,即启发式算法进行优化。
启发式算法的核心:
1.设立一个估价函数,对于每一个状态,估价函数的值就是该状态到目标状态所需的花费。注意这个花费必须小于真实花费,否则程序会出错。
2.对每一次搜索到的状态,我们定义该状态的值为“该状态的花费”+“该状态到目标状态所需的值”,即估价函数在该状态下的值。
3.然后我们每次取出状态值最小的点进行bfs,即可优化搜索。
由此,A*,即是改变bfs的搜索方向,使得我们更快地搜到目标值。
我们再回到本题,在该题中,估价函数应该是该点到目标点的距离,此时估价花费 <= 真实花费,因此可行。
我们定义每个点的值为当前到该点的距离 + 该点到目标点的距离,然后再进行bfs优先队列搜索,即为A*算法在本题的应用。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int M = 1e5+100;
const int N = 2000;
const int inf = 0x3f3f3f3f;
int n,m,s,t,k;
struct Edge{
int to,next,w;
}e[M],ef[M];
int head[N],headf[N],tot,totf;
int dis[N],book[N],dist[N];
struct Po{
int id; //到达哪一个点
int fw; //到该点已经走过的距离
int w; //到该点已经走过的距离+该点距离终点的距离
}tmp;
bool operator < (Po x,Po y) //优先队列只能重载 <
{
return x.w>y.w; //如果放在结构体内重载,需要加const
}
void init()
{
tot = 1; totf = 1;
memset(head,0,sizeof head);
memset(headf,0,sizeof headf);
}
void add(int x,int y,int w)
{
e[++tot].to = y; e[tot].next = head[x]; head[x] = tot; e[tot].w = w;
}
void addf(int x,int y,int w)
{
ef[++totf].to = y; ef[totf].next = headf[x]; headf[x] = totf; ef[totf].w = w;
}
void spfa(int x)
{
queue<int> q;
memset(book,0,sizeof book);
rep(i,1,n) dis[i] = inf;
while(!q.empty()) q.pop();
q.push(x);
book[x] = 1;
dis[x] = 0;
while(!q.empty())
{
int p = q.front();
for(int i = headf[p]; i ; i = ef[i].next)
{
if(dis[ef[i].to] > dis[p]+ef[i].w)
{
dis[ef[i].to] = dis[p]+ef[i].w;
if(book[ef[i].to] == 0)
{
q.push(ef[i].to);
book[ef[i].to] = 1;
}
}
}
book[p] = 0;
q.pop();
}
}
int bfs()
{
if(dis[s] == inf) return -1; //如果不加这句话,就是wa,确保连通性
int val = 0;
priority_queue<Po> q;
while(!q.empty()) q.pop();
tmp.fw = 0; //走到id这个点目前所需要的距离
tmp.id = s;
tmp.w = tmp.fw+dis[tmp.id];
q.push(tmp);
if(t==s)k++; //这里也是一个大坑点
while(!q.empty())
{
Po p = q.top();
q.pop(); //优先队列,必须在push之前就pop,不然队列顶的那个点会被调到底下去
int id = p.id; //p所在的点
if(id == t) val++;
if(val == k) return p.fw; //求第k短路
for(int i = head[id]; i ; i = e[i].next) //加入新边
{
tmp.fw = p.fw+e[i].w;
tmp.id = e[i].to;
tmp.w = tmp.fw+dis[tmp.id]; //此处为估价函数
q.push(tmp);
}
}
if(val!=k){return -1;}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
rep(i,1,m)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
addf(y,x,z);
}
scanf("%d%d%d",&s,&t,&k);
spfa(t);
printf("%d\n",bfs());
}
return 0;
}
/*
2 10
1 2 42
1 2 1
1 1 79
2 2 65
2 2 82
1 2 92
2 1 28
2 1 5
2 1 93
2 1 17
1 2 1000
*/