- admin's blog
【模板】A*算法,k短路
- @ 2024-1-10 11:20:27
【模板】A*算法,k短路
K短路问题是指我们在图中,寻找到从 s->t 的一条路径,并且该路径是所有 s->t 路径中能长度第k短的。
~个人认为是双向Dijsktra+剪枝~
算法核心
- "减枝" 优化
- 估价函数:g(x)=f(x)+w
- Dijkstra最短路
"减枝" 优化
对于问题中所需要求的k短路,我们肯定是要多次访问某些节点的,所以我们会重复走某些边
而在普通的Dijkstra中我们是会 "减枝" 的,因为我们搜索时遇到了第一次进入该节点的时候,距离肯定是当前所有节点的最小值,那么当他最小值的时候往其他方向跑的距离肯定是最短的,后续再经过该节点的时候距离值肯定不会是最优的(因为路径值会一直增大)
同理,最短路(第1短路),只有第一次经过该节点的值有贡献,第k短,就是前k次经过节点的路径值有贡献,其他的路径值都是无用贡献。
如下图:

如果我们要从1->4,的前k小,那么肯会多走2<->3这条路,并且2节点最多经过4次,就可以了。
总结: 前k次经过某个节点的值对答案有贡献,后续就不需要经过该节点了。这种做法就把某些情况给剪掉了。
估价函数:g(x)=f(x)+w
含义: 假如我们已经到了某个点并且已经跑了w的距离,而为了能更快的到t点,我们会记录一个 估计函数f(x),该函数记录的是该点到t点的最短路径,然后实现过程我们跑g(x)最小值。
原因: 因为我们要跑图的时候,可能会出现:当前已走的距离较短,但是该点跑到t点的距离较长,如下图情况:

在该图中,1->2 、 1->3 、 2<->3;这几条路径的值都很小,如果按照传统的思路,我们每次都走当前能走的距离的最短的话,那么肯定会在某些节点反复横跳。
为了避免这种情况,我们排序的时候同时考虑估计函数f(x),也就是我们每次排序都是 当前走的距离+当前节点到目的地的最短距离,这样就能优化掉反复横跳的情况。
解决方法: Dijkstra跑反图,并且"起点"是我们要的终点
总结: 当前走的距离+当前节点到目的地的最短距离进行排序。
code
void dijkstra(){
memset(f,inf,sizeof(f));
priority_queue<node1>q;
q.push( { t,0 } );
f[t]=0;
while( !q.empty() )
{
node1 l=q.top();
q.pop();
int y=l.to,w=l.w;
if( vis[y] ) continue;
vis[y]=1;
for(int i=head2[y];i;i=edge2[i].nex)
{
int x=edge2[i].to;
if( f[x]>f[y]+edge2[i].w )
{
f[x]=f[y]+edge2[i].w;
q.push( { x,f[x] } );
}
}
}
}
Code
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1012,M=100012,inf=0x3f;
int n,m,f[N],cnt,tot,head1[N],head2[N];
int s,t,k,num[N];
bool vis[N];
struct {
int to,nex,w;
}edge1[M],edge2[M];
struct node1{
int to,w;
bool operator < (const node1 &other) const
{
return other.w<w;
}
};
struct node2{
int to,w;
bool operator < ( const node2 &other ) const
{
return other.w+f[other.to]<w+f[to];
}
};
void dijkstra(){
memset(f,inf,sizeof(f));
priority_queue<node1>q;
q.push( { t,0 } );
f[t]=0;
while( !q.empty() )
{
node1 l=q.top();
q.pop();
int y=l.to,w=l.w;
if( vis[y] ) continue;
vis[y]=1;
for(int i=head2[y];i;i=edge2[i].nex)
{
int x=edge2[i].to;
if( f[x]>f[y]+edge2[i].w )
{
f[x]=f[y]+edge2[i].w;
q.push( { x,f[x] } );
}
}
}
}
int astart(){
priority_queue<node2>q;
q.push({s,0});
while( !q.empty() )
{
node2 l=q.top();
q.pop();
int y=l.to,w=l.w;
num[y]++;
if( num[t]==k ) return w;
for(int i=head1[y];i;i=edge1[i].nex)
{
int x=edge1[i].to;
if( num[x]<k )
{
q.push( {x,w+edge1[i].w} );
}
}
}
return -1;
}
void addedge1(int u,int v,int w)
{
cnt++;
edge1[cnt].to=v;
edge1[cnt].nex=head1[u];
edge1[cnt].w=w;
head1[u]=cnt;
}
void addedge2(int u,int v,int w)
{
tot++;
edge2[cnt].to=v;
edge2[cnt].nex=head2[u];
edge2[cnt].w=w;
head2[u]=tot;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
addedge1(u,v,w);
addedge2(v,u,w);
}
cin>>s>>t>>k;
if( s==t ) k++;
dijkstra();
cout<<astart();
}