题目大意就是给出一个图,然后一个起点一个终点,求这两点间的第K短路。
一般做法:SPFA+A*
k短路的话,我们可以马上想到一个宽搜的做法,从st开始宽搜,当第k次搜索到ed时,所得到的长度即所求。
但是这种方法产生特别多的冗杂状态,因为我们每次的选择是无目标的,那么这时候我们就想:怎么能使得我们的选择有目标呢?
那么就要用到A*惹。
A*的精髓就在估价函数
f(x)
f
(
x
)
,具体是什么样子的呢。
我们设图中的一个点x,从起点到x已经走过的距离为
g(x)
g
(
x
)
,到达终点要走的最短距离
h(x)
h
(
x
)
。那么有
f(x)=g(x)+h(x) f ( x ) = g ( x ) + h ( x )
其中
f(x)
f
(
x
)
为x的估价函数,当我们在x点的时候,我们会根据估价函数的大小来选择下一条延展的边,这样带有指向性的选择可以加强算法的效率。
新的问题是,当我们到点x的时候,我们只知道
g(x)
g
(
x
)
,但
h(x)
h
(
x
)
是不知道的。
那么我们只要建一个反图,从ed向st跑一边最短路,得到的dis就是
h(x)
h
(
x
)
啦。
那么A*部分的实现思路就是:
用一个堆来维护所有状态的估价函数值,每次从堆中取出估价函数最小的状态,枚举下一步走的边,将新的状态及其估价值放入堆中,直到找到k条st到ed的路径为止。
注意当st=ed的时候要算(k+1)短路,因为st到ed这条边权为0的边是不能算进k短路里面的。
好啦,A*就这样讲完啦,大家结合代码理解一下吧!
觉得同学的模版写的特好看,就不特地去写了(懒
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
int x,y,d,next;
}a[510000],b[510000];
int len1,last1[11000],len2,last2[11000];
struct As
{
int f,g,to;//f==d[x]+g,用来维护优先队列 g表示出发走了多少距离,to相当于边目录的y
bool operator<(const As a)const
{
if(a.f==f)return a.g<g;
return a.f<f;
}
};
priority_queue<As>Q;
void ins1(int x,int y,int d)
{
len1++;
a[len1].x=x;a[len1].y=y;a[len1].d=d;
a[len1].next=last1[x];last1[x]=len1;
}
void ins2(int x,int y,int d)
{
len2++;
b[len2].x=x;b[len2].y=y;b[len2].d=d;
b[len2].next=last2[x];last2[x]=len2;
}
int st,ed,k,d[11000],list[11000];
bool v[11000];
void SPFA()
{
memset(d,63,sizeof(d));d[ed]=0;
memset(v,false,sizeof(v));v[ed]=true;
int head=1,tail=2;list[1]=ed;
while(head!=tail)
{
int x=list[head];
for(int k=last2[x];k;k=b[k].next)
{
int y=b[k].y;
if(d[y]>d[x]+b[k].d)
{
d[y]=d[x]+b[k].d;
if(v[y]==false)
{
v[y]=true;
list[tail]=y;
tail++;if(tail==10000)tail=1;
}
}
}
v[x]=false;
head++;if(head==10000)head=1;
}
}
void A_star()
{
int cnt=0;As x,y;
x.to=st,x.g=0,x.f=x.g+d[st];
Q.push(x);
while(Q.empty()==false)
{
x=Q.top();Q.pop();
if(x.to==ed)//由于优先队列,先找到的肯定是最好的,由此类推
{
cnt++;
if(cnt==k){printf("%d\n",x.g);return ;}
}
for(int k=last1[x.to];k;k=a[k].next)
{
y.to=a[k].y;
y.g=x.g+a[k].d;
y.f=y.g+d[y.to];
Q.push(y);
}
}
printf("-1\n");
}
int main()
{
int n,m,X,Y,D;
scanf("%d%d",&n,&m);
len1=0;memset(last1,0,sizeof(last1));
len2=0;memset(last2,0,sizeof(last2));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&X,&Y,&D);
ins1(X,Y,D);ins2(Y,X,D);
}
scanf("%d%d%d",&st,&ed,&k);
SPFA();
if(st==ed)k++;
if(d[st]>999999999){printf("-1\n");return 0;}
A_star();
return 0;
}
但是!!
即使用A*优化过,这样的算法还是非常的慢的!
时间复杂度是O(nk)!!
高级做法:可持久化堆(可并堆)
首先推荐大家看论文:《俞鼎力-堆的可持久化与k短路》
然后我还没学完呢,先发着这篇博客先