k短路,顾名思义,由s到t第k短的路径,那么我们该如何解决这个问题?

引子——次短路

次短路:求出从s到t的第二短路径,考虑做法:

首先我们可以直接做出最短路,然后考虑:我们有很多种路径可以走到t,点与点之间由很多边我们没有走,那么我们很容易想到:我们每次松弛操作时都是将点与点最近的走法找到了,那如果我们选择一个第二优秀的走法,不就可以了?

那么考虑:我们需要修改最短路径上的几条边?

直觉是:一条,原因:假设需要修改边\(e_1\)和\(e_2\)可以使答案最优,那很显然:只修改\(e_1\)或者\(e_2\)一定比当前答案更小并且比最短路大。

直接给出代码:

#include

#include

#include

#include

#include

#include

using namespace std;

typedef pair P;

int head[2000005],top;

struct node{

int to,next,dis;

} a[2000005];

int dis1[10005];

int dis2[10005];

void add(int u,int v,int w){

a[++top].next=head[u];

a[top].to=v;

a[top].dis=w;

head[u]=top;

}

void dij(int x){

memset(dis1,0x3f,sizeof(dis1));

memset(dis2,0x3f,sizeof(dis2));

priority_queue,greater

>q;

dis1[x]=0;

q.push(make_pair(0,x));

while(!q.empty( )){

int w=q.top( ).first;

int u=q.top( ).second;

q.pop( );

if(dis2[u]

continue;

for(int i=head[u];i!=-1;i=a[i].next){

int v=a[i].to;

int cost=w+a[i].dis;

if(dis1[v]>cost)

swap(dis1[v],cost),

q.push( P(dis1[v],v));

if(dis2[v]>cost&&cost>dis1[v])

swap(dis2[v],cost),

q.push(P(dis2[v],v));

}

}

}

int main(){

int n,r;

scanf("%d%d",&n,&r);

memset(head,-1,sizeof(head));

int u,v,w;

for(int i=1;i<=r;i++){

scanf("%d%d%d",&u,&v,&w);

add(u,v,w);

add(v,u,w);

}

dij(1);

printf("%d\n",dis2[n]);

return 0;

}

那么如何引申到k短路呢?

思考——k短路

显然,只修改一条边,做k次肯定不对,原因如下: ,

1 显然当k>e时就做不了了(当然会有这样的K)

2 存在x条次优边的和大于某条此优边

那么我们就需要一些非常bt的算法了

前车之鉴——Solution1 A*

首先我们得有一个类似于博弈的思想:次短路和最短路的不同在于失策一步(即以此非最有决策)。也就是说:你按着最短路向终点走,有一步失误了(毕竟人在河边走,哪有不湿鞋),然后紧接着你按着现在的最短方向走到了终点,那么这条路也许就是次短路。是不是次短路取决于你是哪一步走错了。而这一步,可能会让后续的答案非常非常大!!!!!或者很小。

那么此时,我们需要简单估计出这次失误的影响,来粗略判断它究竟可能时第几小。由此,有了 A*算法的估价函数。

Point 1 用 F(x) 表示以当前的走法走到 x 的距离,H(X) 表示 x 到终点的最短路长度。

--Note 1 那么对于 x 节点,我们下一步可以选择 “走对” 或者 “走错”,于是这一个 x 又可以染色到相邻的节点。

Point 2 每一次我们选择 F(x)+H(X) 最小的 x 节点染色,那么最先到达终点时终点的 F(x) 就是最短路 (很像 dijstra的贪心思想),那么显然:第二次到达终点就是次短路。

--Note 2 原因: 简而言之,我们的染色方式确保了被染色节点的 F(x)+H(x) 一定是递增的,而到达了终点,H(x)=0,那么 F(x) 就是递增的。(者可以用bfs思想来理解)

这种算法在随机数据下,若边权较小,同时k 较大,速度不如上一迭代加深搜索。但是它的优点是稳定。

PS:由于迭代加深这个算法实在实在太太太bt了,我们......不予赘述(百度,让世界更美好)

时间复杂度 \(O(NKlog_2⁡K)\)

那么作为一个老图伦选手,我们绝壁在想:这中算法就和SPFA一样,肯定可以卡,于是:线索形成思想,然后......以后你做K短路又少了一种满分写法......考虑一种卡 A* 的图:构造一个大环,包含起始节点和终止节点即可。(相信你完全理解)

#include

#include

#include

#include

#define MX 10001

#define ME 100001

using namespace std;

int n,m,k;

int S,T;

struct Node{

int p,f,h;

bool operator <(const Node a)const{

if(a.f+a.h

else return 0;

}

Node(int a,int b,int c){

this->p=a,this->f=b,this->h=c;

}

};

Node make(int a,int b,int c){

Node thi(a,b,c);

return thi;

}

int dis[MX];

//封装

class graph{

public:

int fst[MX],nxt[ME],v[ME],w[ME],lnum;

int q[MX],inq[MX];

priority_queue mp;

void init(){

memset(fst,0xff,sizeof(fst));

lnum=-1;

}

void addeg(int nu,int nv,int nw){

nxt[++lnum]=fst[nu];

fst[nu]=lnum;

v[lnum]=nv;

w[lnum]=nw;

}

//走最短路进行估价

void SPFA(int frm){

int h=0,t=1,x,y;

memset(dis,0x3f,sizeof(dis));

q[++h]=frm;

dis[frm]=0;

inq[frm]=1;

while(h>=t){

x=q[(t++)%ME];

for(int i=fst[x];i!=-1;i=nxt[i]){

y=v[i];

if(dis[y]>dis[x]+w[i]){

dis[y]=dis[x]+w[i];

if(!inq[y]){

q[(++h)%ME]=y;

inq[y]=1;

}

}

}

inq[x]=0;

}

}

int Astar(int frm,int to){

Node x(0,0,0);

int cnt=0;

if(frm==to)k++;

if(dis[frm]>100000)return -1;

mp.push(make(frm,0,dis[frm]));

while(!mp.empty()){

x=mp.top(),mp.pop();

if(x.p==to){

cnt++;

if(cnt==k)return x.f+x.h;

}

for(int i=fst[x.p];i!=-1;i=nxt[i])mp.push(make(v[i],x.f+w[i],dis[v[i]]));

}

return -1;

}

}g1,g2;

void input(){

int a,b,c;

scanf("%d%d",&n,&m);

for(int i=1;i<=m;i++){

scanf("%d%d%d",&a,&b,&c);

g1.addeg(a,b,c);

g2.addeg(b,a,c);

}

scanf("%d%d%d",&S,&T,&k);

}

int main(){

g1.init(),g2.init();

input();

g2.SPFA(T);

printf("%d\n",g1.Astar(S,T));

return 0;

}

后车方向盘——Solution2 可持久化可并堆

PS:我们一般用可持久化左偏树,原因:代码比较好些,性质比较优秀,当然,斐波那契堆也是很棒的,但是说实话,考场打这种算法......真的有点顶。言归正传:

其实,可并堆的做法是对 A* 做法的一种改进。

由于 A* 做法可能在多次扩展后才能找到一条路径,浪费时间(等于浪费生命),也使得时间复杂度变得非常大。

如果可以在每次扩展时都能得到一条路径,就可以将时间优化到 \(O(K\log N)\) 这个级别。

怎么做到这样呢?我们先将反图的最短路树建立出来。任何一条 S-T 路径都可以表示为若干树边和非树边交替出现的序列。我们可以直接使用其非树边序列表示这个序列。

现在定义几个东西:

dis(x):x点沿着最短路树走到 T的路径长度。

\(e.s,e.t,e.w\):非树边 e的起始节点,终止节点和长度。

\(\Delta e\):从\(e.s\)出发走非树边e,再走最短路,比直接走\(e.s\)的最短路多走的距离

一条路径的长度,可以表示为每条非树边多走的距离之和。即 \(\sum \Delta e\)

如果找到一条合法的路径 \(e_1,e_2,\cdots ,e_m\) ,考虑如何像 A* 算法,找一个比这条路径略长的新的路径。保证每次找到的路径不重复。

很容易:有两种方式可以找到一条新的合法路径,并且这条路径尽可能的短,而且可以保证不重复。

Way 1 将 \(e_m\)替换成一条\(\Delta\)更大的非树边

Way 2 将序列后面添加上一条 \(\Delta\)最小的可行非树边即\(e_1,e_2⋯,e_m,e_{m+1}\)

考虑正确性:将非树边序列映射到一张新的图上。将 \(e_i\)映射为新图的点,如果\(ej⋯ \cdots e_i,e_j\cdots\) 是一个合法的路径 (即 \(e_i.t\) 可以通过树边走到 \(e_j.s\) ),那么 \(e_i\)和\(e_j\)之间存在一条边。现在即:求这张新的图的不规定终点的 k 短路。于是,算法的正确性就非常显然了。

对于所有的非树边序列,只需保存它的\(\sum \Delta e\)以及\(e_m\)即可。每次,从所有的已知的非树边序列中选择\(\sum \Delta e\)最小的那一个作为新的路径,并且从它开始进行扩展,将扩展出的新序列加入已知序列的集合中。已知序列的集合需要用堆 (优先队列) 维护。

接下来就是如何扩展的问题了。先考虑如何找到一条边的所有可行后继非树边。这些边就是所有挂在最短路树上\(e_m.t\cdots T\)上的非树边。也就是说一条边的可行后继非树边集合为 \(e.t\)在最短路树上的父亲的集合并上挂在\(e.t\)上的非树边。由于我们在扩展时寻找的是最小可行非树边,因此将这个集合用可并堆维护,每个点的集合由其父亲的集合继承而来。这样,一条非树边的最小可行后继非树边就是 \(e.t\) 的可并堆的跟。

如何替换一条边呢?一条边所在的可并堆的两个儿子可以替换这条边。

综上,就是 \(O(K\log_2 N)\) 的 K 短路做法。

需要注意几个地方:

当所有的路径找完了,需要及时退出,以免 pop一个空的优先队列。

当图中根本就没有第二条路径,S的可并堆将是空的,这时候需要直接退出。

时间复杂度:\(O(T(shortest path)+nlog_2n+klog_2n)\)

下面是魔法猪学院的代码。(洛谷\(P2483\))

#include

#include

#include

#include

#include

#include

using namespace std;

const int N = 5050;

const int M = 200050;

const int MAXN = 10*M;

const double eps = 1e-8;

int n,m,fa[N],tot,rt[N],pre[N];

double lim,g[N];

bool vis[N];

struct node{

int ls,rs,dis,tl;

double v;

}p[MAXN];

int merge(int x,int y){

if(!x||!y)return x+y;

if(p[x].v-p[y].v>=eps)swap(x,y);

int u = ++tot;

p[u] = p[x];

p[u].rs = merge(p[u].rs,y);

if(p[p[u].ls].dis

p[u].dis = p[p[u].rs].dis+1;

return u;

}

typedef pair P;

int hed[N],cnt=1;

bool cov[2*M];

struct edge{

int to,nxt;

double w;

}e[2*M];

void ae(int f,int t,double w){

e[++cnt].to = t;

e[cnt].nxt = hed[f];

e[cnt].w = w;

hed[f] = cnt;

}

void dij(){

priority_queue,greater

> q;

for(int i=1;i

q.push(P(0.0,n));

while(!q.empty()){

P tp = q.top();

q.pop();

int u = tp.second;

if(vis[u])continue;

vis[u] = 1;

for(int j=hed[u];j;j=e[j].nxt){

if(j&1){

int to = e[j].to;

if(g[to]+eps>g[u]+e[j].w){

g[to] = g[u]+e[j].w;

q.push(P(g[to],to));

}

}

}

}

}

void dfs(int u){

vis[u] = 1;

for(int j=hed[u];j;j=e[j].nxt)if(j&1){

int to = e[j].to;

if(fabs(g[to]-(e[j].w+g[u]))<=eps&&!vis[to]){

fa[to] = u;

cov[j^1] = 1;

dfs(to);

}

}

}

void topo(){

priority_queue

q;

for(int i=1;i<=n;i++)q.push(P(g[i],i));

for(int u,i=1;i<=n;i++){

u = q.top().second;q.pop();

if(fa[u])rt[u] = merge(rt[u],rt[fa[u]]);

}

}

int main(){

scanf ("%d%d",&n,&m);

scanf("%lf",&lim);

int f,t;

double w;

for(int i=1;i<=m;i++){

scanf ("%d%d",&f,&t);

scanf("%lf",&w);

ae(f,t,w);

ae(t,f,w);

}

dij();

memset(vis,0,sizeof(vis));

dfs(n);

for(int j=2;j<=cnt;j+=2){

if(!cov[j]){

int f = e[j^1].to,t = e[j].to;

double w = g[t]+e[j].w-g[f];

p[++tot].v = w,p[tot].dis = 1,p[tot].tl = t;

rt[f] = merge(rt[f],tot);

}

}

topo();

priority_queue

q;

int ans = 0;

if(lim+eps>g[1])

lim-=g[1],ans++;

if(rt[1])q.push(P(p[rt[1]].v,rt[1]));

while(!q.empty()){

P tp = q.top();

q.pop();

if(lim+eps>tp.first+g[1])

lim-=(tp.first+g[1]),ans++;

else

break;

int u = tp.second,ls = p[u].ls,rs = p[u].rs;

if(ls)

q.push(P(tp.first-p[u].v+p[ls].v,ls));

if(rs)

q.push(P(tp.first-p[u].v+p[rs].v,rs));

int t=p[u].tl;

if(rt[t])

q.push(P(p[rt[t]].v+tp.first,rt[t]));

}

printf("%d\n",ans);

return 0;

}

//本代码来自博客 https://www.mina.moe/archives/2777 ,疯狂安利这个博主,写得很很很很好,大赞!

#include

#include

#include

#include

#include

#include

#define MX 200008

#define oo 123123123123123

using namespace std;

template void read(T& x){

x = 0; char c = getchar(); bool f = 0;

while(!isdigit(c) && c!='-') c = getchar();

if(c == '-') f = 1, c = getchar();

while(isdigit(c)) x = x*10+c-'0', c = getchar();

if(f) x = -x;

}

int N, M;

double E;

struct PNODE{

int p;

double d;

PNODE (const int& p0 = 0, const double& d0 = 0) : p(p0), d(d0) {}

bool operator < (const PNODE& t) const {return d > t.d;}

};

struct SNODE{

int ls, rs, ed, len;

double w;

SNODE (const int& e0 = 0, const double& w0 = 0) : ls(0), rs(0), ed(e0), len(0), w(w0) {}

};

struct HEAP{

int cnt;

SNODE tre[MX*6];

int merge(int x, int y){

int a;

if(!x || !y) return (x|y);

if(tre[x].w < tre[y].w) swap(x, y);

tre[a = ++cnt] = tre[y];

tre[a].ls = merge(x, tre[y].ls);

if(tre[tre[a].ls].len > tre[tre[a].rs].len) swap(tre[a].ls, tre[a].rs);

tre[a].len = tre[tre[a].ls].len + 1;

return a;

}

};

struct GRAPH{

int fst[MX], nxt[MX], v[MX], lnum;

int que[MX], inq[MX], pre[MX];

double w[MX], dis[MX];

void addeg(int nu, int nv, double nw){

nxt[++lnum] = fst[nu];

fst[nu] = lnum;

v[lnum] = nv;

w[lnum] = nw;

}

void spfa(int frm){

int h = 1, t = 1, x, y;

for(int i=1; i<=N; i++) dis[i] = +oo;

que[h] = frm;

dis[frm] = 0;

inq[frm] = 1;

while(h >= t){

x = que[(t++)%MX];

inq[x] = 0;

for(int i=fst[x]; i; i=nxt[i]){

y = v[i];

if(dis[y] > dis[x] + w[i]){

dis[y] = dis[x] + w[i];

pre[y] = i;

if(!inq[y]){

que[(++h)%MX] = y;

inq[y] = 1;

}

}

}

}

}

};

HEAP H;

GRAPH G, R;

priority_queue Q;

void input(){

int a, b; double c;

read(N); read(M); scanf("%lf", &E);

for(int i=1; i<=M; i++){

read(a); read(b); scanf("%lf", &c);

G.addeg(a, b, c);

R.addeg(b, a, c);

}

}

PNODE seq[MX];

int rot[MX];

void work(){

R.spfa(N);

for(int i=1; i<=N; i++) seq[i] = PNODE(i, R.dis[i]);

sort(seq+1, seq+N+1);

for(int a=N; a>=1; a--){

int x = seq[a].p;

for(int i=G.fst[x]; i; i=G.nxt[i]){

if(i != R.pre[x]){

H.cnt++;

H.tre[H.cnt] = SNODE(G.v[i], R.dis[G.v[i]]-R.dis[R.v[i]]+G.w[i]);

rot[x] = H.merge(rot[x], H.cnt);

}

}

rot[x] = H.merge(rot[x], rot[G.v[R.pre[x]]]);

}

if(rot[1]) Q.push(PNODE(rot[1], H.tre[rot[1]].w));

int cnt = 0;

if(E-R.dis[1] >= 0) E -= R.dis[1], cnt++;

while(E > 0){

if(Q.empty()) break;

PNODE e = Q.top();

Q.pop();

if(E-(e.d+R.dis[1]) < 0) break;

else E -= (e.d+R.dis[1]), cnt++;

if(H.tre[e.p].ls) Q.push(PNODE(H.tre[e.p].ls, e.d - H.tre[e.p].w + H.tre[H.tre[e.p].ls].w));

if(H.tre[e.p].rs) Q.push(PNODE(H.tre[e.p].rs, e.d - H.tre[e.p].w + H.tre[H.tre[e.p].rs].w));

if(rot[H.tre[e.p].ed]) Q.push(PNODE(rot[H.tre[e.p].ed], e.d + H.tre[rot[H.tre[e.p].ed]].w));

}

printf("%d\n", cnt);

}

int main(){

input();

work();

return 0;

}

小结

虽然到最后我们都没有明白:为什么要绕路,但是,我们已经解决了这个k短路的问题,

Copyright © 2088 2017乒乓球世界杯_世界杯体彩 - uzhiqu.com All Rights Reserved.
友情链接