其实这是一个很古老的姿势啦…
只不过今天跟同学讨论A*算法求k短路的时候,同学不信A*算法能被卡掉.
于是我翻了翻课件找出了一种n元环的特殊情况,卡掉了A*算法.
A*算法是只有到达终点的时候才能统计答案,这导致可能拓展很多个状态才能得到一个用来更新答案的有效状态.
例如一个n元环,当我们到达终点之后,可能还要拓展n次才能得到下一个状态.于是若求k短路时间复杂度就为O(nk).于是就容易被卡掉.
我们考虑换一种方式来定义一条从起点s到终点t的路径.
构建出以t为终点的最短路树,t是这棵树的根,对于剩下的所有点x都有一个父亲点pax表示x到t的最短路上x的后继.若存在多个后继,则随意选择一个.
现在我们有了一个唯一的树结构.
考虑一条路径,这是一个边序列,其中有树边和非树边,我们将其中的树边去掉,仅考虑非树边.
令dist(x,y)表示从x到y的最短路径长度,则走一条非树边x→y会使答案增加len(x,y)?(dist(x,t)?dist(y,t)).
(珍爱生命,远离微软输入法!)
我们可以预先处理出从s到t的最短路径长度,于是对于所有路径,求出其中非树边的代价和,再加上s,t最短路长度就是我们想要的答案.
于是我们将问题转化为第k小的合法非树边序列的代价和.
首先我们需要证明一个事情,就是一个合法的非树边序列唯一对应一条从s到t的路径.
让我们先来看看一个非树边序列是合法的条件:
考虑在序列中相邻的两条边e,f,那么一定满足条件head(f)在tail(e)到根的路径上.
这是因为在两条路径之间只能走树边,而树边必定是指向根节点的,所以深度必定减少,因此上述结论是成立的.
由这个结论,我们显然可以发现一个非树边序列唯一对应一条从s到t的路径.
我们可以由这个性质对于非树边序列进行拓展:
假如某个状态的最后一条非树边是e,我们就把所有起点在tail(e)到根的路径上的非树边附加在e的后面当做一个新的状态拓展下去.
同时依然利用优先队列维护即可.
然而这样是极其爆炸的.对于一个状态,我们最多可以拓展出O(m)个状态,也就是说总的状态数为O(km),无法接受.
我们不妨考虑对于所有的点维护一个堆存下所有起点在这个点到根路径上的非树边,这样当我们从一个非树边序列向下拓展时,只有以下两种情况:
在结尾加上一条非树边:只需要取最后一条边的终点的堆中的最小边即可.
将最后一条边替换为另一条边:只需要将序列中的最后一条边替换为堆中这条边的儿子即可.
用可持久化的堆来维护这些边即可.
时间复杂度O((n+m)logn+mlogm+klogk).
这样就可以了.是很稳定的算法.
下面是我的代码.
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define mp make_pair
#define pb push_back
#define inf 0x3f3f3f3f
#define N 10010
int n,m,K;
typedef long long ll;
typedef pair<int,int> pii;
struct SegmentTree{
pii s[32768];
int M;
inline void init(int _siz){
for(M=1;M<(_siz+2);M<<=1);
for(int i=0;i<(M<<1);++i)
s[i]=mp(inf,0);
}
inline void modify(int x,int y){
for(s[x+M]=mp(y,x),(x+=M)>>=1;x;x>>=1)
s[x]=min(s[x<<1],s[x<<1^1]);
}
inline int getid(){
return s[1].second;
}
}Seg;
struct Graph{
static const int V=N;
static const int E=100010;
int head[V],next[E],end[E],len[E],ind;
inline void reset(){
ind=0;
memset(head,0,sizeof head);
}
inline void addedge(int a,int b,int c){
int q=++ind;
end[q]=b;
next[q]=head[a];
head[a]=q;
len[q++]=c;
}
}g,_g,tree;
int d[N];
inline void dijkstra(int s,Graph&g){
memset(d,0x3f,sizeof d);
Seg.init(n);
d[s]=0;
Seg.modify(s,0);
for(int i=1;i<=n;++i){
int x=Seg.getid();
for(int j=g.head[x];j;j=g.next[j]){
if(d[g.end[j]]>d[x]+g.len[j]){
d[g.end[j]]=d[x]+g.len[j];
Seg.modify(g.end[j],d[g.end[j]]);
}
}
Seg.modify(x,inf);
}
}
int pa[N];
struct Node{
Node*l,*r;
int v,tail,dist;
Node():dist(0){}
}mempool[1000010],*P=mempool,Tnull,*null=&Tnull;
inline Node*newnode(int _v,int _tail){
P->l=P->r=null;
P->v=_v;
P->tail=_tail;
P->dist=1;
return P++;
}
inline void copy(Node*&p,Node*q){
if(q==null)
p=null;
else
*p=*q;
}
inline Node*merge(Node*p,Node*q){
Node*s=P++;
if(p==null||q==null){
copy(s,p==null?q:p);
return s;
}
if(p->v>q->v)
swap(p,q);
copy(s,p);
s->r=merge(p->r,q);
if(s->l->dist<s->r->dist)
swap(s->l,s->r);
s->dist=s->r->dist+1;
return s;
}
Node*root[N];
struct State{
ll ldist;
Node*ledge;
State():ldist(0ll),ledge(null){}
State(ll _ldist,Node* _ledge):ldist(_ldist),ledge(_ledge){}
bool operator<(const State&B)const{
return ldist>B.ldist;
}
};
priority_queue<State>Q;
bool treeedge[100010];
int main(){
cin>>n>>m>>K;
int i,j,a,b,c;
g.reset();
_g.reset();
for(i=1;i<=m;++i){
scanf("%d%d%d",&a,&b,&c);
g.addedge(a,b,c);
_g.addedge(b,a,c);
}
dijkstra(n,_g);
for(i=1;i<n;++i)
for(j=g.head[i];j;j=g.next[j])
if(d[i]==d[g.end[j]]+g.len[j]){
pa[i]=g.end[j];
treeedge[j]=1;
break;
}
tree.reset();
for(i=1;i<n;++i)
tree.addedge(pa[i],i,0);
queue<int>q;
q.push(n);
Node*p;
root[0]=null;
while(!q.empty()){
i=q.front();
q.pop();
root[i]=merge(null,root[pa[i]]);
for(j=g.head[i];j;j=g.next[j])
if(!treeedge[j]){
p=newnode(g.len[j]-(d[i]-d[g.end[j]]),g.end[j]);
root[i]=merge(root[i],p);
}
for(j=tree.head[i];j;j=tree.next[j])
q.push(tree.end[j]);
}
if(K==1)
cout<<d[1]<<endl;
else{
--K;
Q.push(State(d[1]+root[1]->v,root[1]));
State tmp;
ll ldist;
Node*ledge;
for(int i=1;i<=K;++i){
tmp=Q.top();
Q.pop();
ldist=tmp.ldist;
ledge=tmp.ledge;
if(i==K){
cout<<ldist<<endl;
break;
}
Q.push(State(ldist+root[ledge->tail]->v,root[ledge->tail]));
if(ledge->l!=null)
Q.push(State(ldist-ledge->v+ledge->l->v,ledge->l));
if(ledge->r!=null)
Q.push(State(ldist-ledge->v+ledge->r->v,ledge->r));
}
}
return 0;
}
就这样吧…