K短路
引入
对于最短路,我们可以用Dijstra,Spfa,Floyd\rm Dijstra,Spfa,FloydDijstra,Spfa,Floyd等算法求出。
那么对于第KKK短的路,我们该如何求取呢?
Ps. 这里都是在有向图上求取KKK短路,无向图上可以将其建为双向边然后跑有向图上的KKK短路即可。
算法一:搜索
我们可以在最短路算法上,轻微改动一下。我们将每一条从起点SSS到终点TTT的路径搜出来,排序后选取第KKK个即可。复杂度为O(w(n+m)+wlogw)O(w(n+m)+wlogw)O(w(n+m)+wlogw),n,mn,mn,m为点数和边数,www为路径总条数。
显然,这样暴力的算法只可能拿到非常少的分。
算法二:搜索剪枝
我们发现,当你搜索的路径数已经达到KKK条后,凡是已经大于你所搜过的最大的路径长度的情况可以统统剪掉,将较小的加入后,我们弹出最大的一条,并将剪枝用的最大值修改成新的最大值,这个最大值肯定是单调递减的,所以可以大大减少搜索量。
但是,对于特殊构造的图,仍然如同暴力。
算法三:启发式搜索
启发式搜索,就是常用的A∗A^*A∗算法。
同样的还是暴力搜索,我们可以对当前的局势进行预估判断,从而达到剪枝的最大化。
所以这里我们设置两个函数ggg和fff,fff表示你当前走到这里,然后走到终点至少还总共会走多少距离;ggg则表示你当前走了多少距离。
所以这里我们要先将图反建(边的方向变反),然后从TTT跑出单源最短路,TTT到点iii的最短路记为dt[i]dt[i]dt[i],预处理了这个我们就能对当前局势进行预判了。
当前的函数ggg的值就等于走来的点的g′g'g′值加上这条边的权值:
g[v]=g[u]+dis[u][v]g[v]=g[u]+dis[u][v]g[v]=g[u]+dis[u][v]
而当前的预判函数fff的值,当然就是当前已经确定的距离加上我们预期能走的最短距离,预期最短距离已经预处理了,所以可以得知:
f[v]=g[v]+dt[v]f[v]=g[v]+dt[v]f[v]=g[v]+dt[v]
然后我们利用前面普通的剪枝方式,按照fff为第一关键字,ggg为第二关键字,将状态排序,每次找最小的出来更新,那么刚好更新到第KKK个的时候,它就是第KKK短的啦。
这里我们就用bfsbfsbfs(宽度优先搜索)加上优先队列就可以做到了。
复杂度最好为O(K+mlogm)O(K+mlogm)O(K+mlogm),最坏的话和暴力差不多,所以只适合没有特别精心构造的图。
下面上博主的简陋代码QWQ:
poj2449 AC
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010,M=1e5+10;
const int inf=0x3f3f3f3f;
int n,m,s,t,k;
struct ss{
int to,last,val;
#define F to
#define G last
#define id val
ss(){}
ss(int a,int b,int c):to(a),last(b),val(c){}
};
struct Graphy{
ss g[M];
int head[M],cnt;
void add(int a,int b,int c){
g[++cnt]=ss(b,head[a],c);head[a]=cnt;
}
void clear(){
memset(head,0,sizeof(head));cnt=0;
}
};
namespace Gra_Inv{
Graphy G;
int que[M],p,q;
bool vis[M];
bool spfa(int st,int en,int *dis){
memset(dis,0x3f,sizeof(int)*(n+1));
dis[en]=0;que[p=q=0]=en;vis[en]=1;
int *head=G.head;ss *g=G.g;
for(;p<=q;p++){
int a=que[p%M],v;
for(int i=head[a];i;i=g[i].last){
v=g[i].to;
if(dis[v]>dis[a]+g[i].val){
dis[v]=dis[a]+g[i].val;
if(!vis[v]){
vis[v]=1;
que[++q%M]=v;
if(dis[que[(p+1)%M]]>dis[que[q%M]]){
swap(que[(p+1)%M],que[q%M]);
}
}
}
}
vis[a]=0;
}
return dis[st]!=inf;
}
}
namespace Gra_Astar{
Graphy G;
struct node{
ss s;
node(){}
node(ss a):s(a){}
bool operator <(const node &a)const
{return s.F>a.s.F||(s.F==a.s.F&&s.G>a.s.G);}
};
priority_queue <node> Q;
int dis[M];
int A_star(int st,int en,int k){
int *head=G.head;ss *g=G.g;
node tmp=node(ss(dis[st],0,st)),now;
Q.push(tmp);
if(st==en)++k;
while(!Q.empty()){
tmp=Q.top();Q.pop();
int a=tmp.s.id,v;
if(a==en) {--k;if(!k)return tmp.s.F;}
for(int i=head[a];i;i=g[i].last){
v=g[i].to;
now.s.G=tmp.s.G+g[i].val;
now.s.F=now.s.G+dis[v];
now.s.id=v;
Q.push(now);
}
}
return -1;
}
}
int a,b,c;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
Gra_Inv::G.add(b,a,c);
Gra_Astar::G.add(a,b,c);
}
scanf("%d%d%d",&s,&t,&k);
if(Gra_Inv::spfa(s,t,Gra_Astar::dis)){
printf("%d\n",Gra_Astar::A_star(s,t,k));
}else puts("-1");
return 0;
}
最短路树加可持久化堆
这个是在A∗A^*A∗算法上和最短路算法上,总结升华提取出来的一个较为稳定的KKK短路算法。
我们同样的按照A∗A^*A∗求KKK短路的思路,我们反向建图,然后跑TTT的单源最短路,我们可以发现,这个跑出来的n−1n-1n−1条最短路上的边,和nnn个点一定构成一棵树。
因为树上的两两点对之间的路径是唯一的(路径为最短路),且这个树的根为TTT
那么我们考虑没有在这颗树上的边(称为非树边),如果我们选择了一条u→vu\rightarrow vu→v的非树边,那么它一定会使得最短路变化(大多数情况下是变大),而变化的值为dis[v]+not_tree_edge[u][v]−dis[u]dis[v]+not\_tree\_edge[u][v]-dis[u]dis[v]+not_tree_edge[u][v]−dis[u](其中dis[v]dis[v]dis[v]表示vvv到TTT的最短路长度)。
所以我们可以将所有的非树边的边权重新设置为dis[v]+not_tree_edge[u][v]−dis[u]dis[v]+not\_tree\_edge[u][v]-dis[u]dis[v]+not_tree_edge[u][v]−dis[u],那么第KKK短路肯定为S→TS\rightarrow TS→T的最短路加上一个S→TS\rightarrow TS→T的非树边序列的权值。
非树边序列:即为你这条路上所走过的非树边,按照从S→TS\rightarrow TS→T的方向。
因为最短路的长度确定了,那么我们只需这个非树边序列的权值,是所有S→TS\rightarrow TS→T中的合法非树边序列权值中的第KKK小即可。
所以我们来考虑,对于每一个点,S→TS\rightarrow TS→T的非树边序列如果经过它的话,一部分的序列肯定是这个点到TTT的非树边序列中的一种,所以对于每一个点,我们用一个小根堆来维护它到TTT的非树边序列值。
那么对于一个点vvv,我们如何快速得到它的小根堆(排好序的非树边序列)呢?
我们可以由当前的点vvv到父亲f[v]f[v]f[v]的非树边加上f[v]f[v]f[v]的小根堆的状态即可,所以为了方便更新和减少空间开销,所以我们要用可持久化堆(这里使用左偏树)。
然后先预处理所有点的序列,然后我们每次取出最小的一个,然后加入新的状态,直到更新到第KKK个,答案就为最短路长度加上第KKK个非树边序列的值即可。
复杂度为O(nlogn+mlogm+klogk)O(nlogn+mlogm+klogk)O(nlogn+mlogm+klogk)
这个复杂度是比较稳定的(缺点是不好记录路径)。
下面上用可持久化左偏树+Spfa\rm SpfaSpfa实现的代码:
注意:有两种情况
- 允许点重复走的:当S=TS=TS=T时,要减去一条SSS到自己的路径。
- 不允许重复走的要记录已经走过的点(一般点数不多,直接boolboolbool数组或者bitsetbitsetbitset记录)
同样是poj的那道题
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int M=7e5+1,N=1e3+10;
const int inf=0x7fffffff;
int n,m;
struct ss{
int to,last,len;
ss(){}
ss(int a,int b,int c):to(a),last(b),len(c){}
};
struct Graphy{
ss g[M];
int head[M],cnt;
void add(int a,int b,int c){
g[++cnt]=ss(b,head[a],c);head[a]=cnt;
}
}G,GI;
struct node{
int val,v;
node(){}
node(int a,int b):val(a),v(b){}
bool operator <(const node &a)const{return val>a.val;}
}pp,qq;
priority_queue <node> Q;
struct Left_Tree{
int h[M],v[M],w[M],son[2][M],tot,root[M];
void clear(){tot=0;}
#define l(o) son[0][o]
#define r(o) son[1][o]
int newnode(int vv,int idd,int hh){
++tot;
w[tot]=vv;v[tot]=idd;h[tot]=hh;
l(tot)=r(tot)=0;
return tot;
}
int merge(int a,int b){
if(!a||!b) return a|b;
if(w[a]>w[b])swap(a,b);
int newp=newnode(w[a],v[a],h[a]);
l(newp)=l(a);r(newp)=r(a);
r(newp)=merge(r(newp),b);
if(h[l(newp)]<h[r(newp)])swap(l(newp),r(newp));
h[newp]=h[r(newp)]+1;
return newp;
}
void insert(int &o,int vv,int idd){
int p=newnode(vv,idd,0);
o=merge(p,o);
}
}LT;
int que[M],p,q;bool in[M];
void spfa(int st,int en,int *dis,Graphy &G){
int *head=G.head,v;ss *g=G.g;
for(int i=1;i<=n;i++)dis[i]=inf;
que[p=q=0]=en;in[en]=1;dis[en]=0;
for(;p<=q;p++){
int a=que[p%M];
for(int i=head[a];i;i=g[i].last){
v=g[i].to;
if(dis[v]>dis[a]+g[i].len){
dis[v]=dis[a]+g[i].len;
if(!in[v]){
in[v]=1;
que[++q%M]=v;
if(dis[que[(p+1)%M]]>dis[que[q%M]]){
swap(que[(p+1)%M],que[q%M]);
}
}
}
}
in[a]=0;
}
}
int fd[M],stk[M],dis[M],top,fs[M];
void dfs(int a){
in[a]=1;stk[++top]=a;int v,w;
for(int i=GI.head[a];i;i=GI.g[i].last){
v=GI.g[i].to;w=GI.g[i].len;
if(!in[v]&&dis[v]==dis[a]+w){
fd[v]=a;fs[i]=1;
dfs(v);
}
}
}
void Rebuild(){
int a,v;
for(int w=1;w<=top;w++){
a=stk[w];
LT.root[a]=LT.root[fd[a]];
for(int i=G.head[a];i;i=G.g[i].last){
v=G.g[i].to;
if(!fs[i]&&dis[v]!=inf){
LT.insert(LT.root[a],dis[v]-dis[a]+G.g[i].len,v);
}
}
}
}
int Solve_K(int st,int en,int kth){
if(!LT.root[st]) return -1;
if(kth==1) return dis[st];
pp.val=dis[st]+LT.w[LT.root[st]];
pp.v=LT.root[st];
Q.push(pp);
while(!Q.empty()){
--kth;
qq=Q.top();Q.pop();
if(kth==1) return qq.val;
int ls=LT.l(qq.v),rs=LT.r(qq.v),o=LT.v[qq.v];
int nowv=qq.val;
if(LT.root[o]) Q.push(node(nowv+LT.w[LT.root[o]],LT.root[o]));
if(ls) Q.push(node(nowv+LT.w[ls]-LT.w[qq.v],ls));
if(rs) Q.push(node(nowv+LT.w[rs]-LT.w[qq.v],rs));
}
return -1;
}
void add_side(int a,int b,int c){
G.add(a,b,c);GI.add(b,a,c);
}
void calc_K(int ss,int tt,int kk){
if(ss==tt)++kk;
spfa(ss,tt,dis,GI);
if(dis[ss]==inf) {puts("-1");return;}
dfs(tt);
Rebuild();
int ans=Solve_K(ss,tt,kk);
printf("%d\n",ans);
}
int s,t,k;
void work(){
int a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
add_side(a,b,c);
}
scanf("%d%d%d",&s,&t,&k);
calc_K(s,t,k);
}
int main(){work();return 0;}
若有不对或者疑问的地方欢迎提出!!
博主才学,可能有不清楚或者不对的地方。QAQ