再探第k短路

其实这是一个很古老的姿势啦…

只不过今天跟同学讨论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;
}

就这样吧…

时间: 2024-10-19 10:13:31

再探第k短路的相关文章

K短路【模板】

A*+SPFA算法: (1)将有向图的所有边正向.反向分别存入两个不同的边集(Edges,Edges1)中.用反向边集,以所求终点t为源点,利用SPFA或Dijkstra求解出所有点到t的最短路径,用Dist[i]数组来表示点i到点t的最短距离. (2)建立一个优先队列,将源点s加入到队列中. (3)从优先队列中取出最小的点p,如果点p == t,则计算t出队的次数.如果当前路径长度就是s到t的第k短路长度,算法结束.否则遍历与p相连的所有的边,将扩展出的到p的邻接点信息加入到优先队列中取. 注

次短路 + 第K短路 模版

虽然从字面上看,次短路和第2短路是一样的.但是我在题目中遇到的却不是这样的. 在有些题目中,需要判断次短路是否存在.比如说,u.v之间只有一条路径.那么只有最短路.次短路是不存在的.这时候,解题方法是先求出最短路,然后枚举删除最短路径中的边,然后求最小值.题目可以看poj3986. 第K短路的实现是 SPFA + A* 算法. A*算法通过一个估价函数f(h)来估计途中的当前点p到终点的距离,并由此决定它的搜索方向,当这条路径失败时,它会尝试其他路径.对于A*,估价函数 = 当前值 + 当前位置

POJ--2449--Remmarguts&amp;#39; Date【dijkstra_heap+A*】第K短路

链接:http://poj.org/problem?id=2449 题意:告诉你有n个顶点,m条边.并把这些边的信息告诉你:起点.终点.权值.再告诉你s.t.k.需求出s到t的第k短路,没有则输出-1. 第K短路裸题,A*算法没接触过.參考了这篇博客:http://www.cnblogs.com/n-u-l-l/archive/2012/07/29/2614194.html 下面大体字摘自这篇博文,讲的非常清楚: 对于第k短路,能够想到的一个比較朴素的算法就是广度优先搜索,使用优先队列从源点s进

POJ 2449 Remmarguts&#39; Date ( 第 k 短路 &amp;&amp; A*算法 )

题意 : 给出一个有向图.求起点 s 到终点 t 的第 k 短路.不存在则输出 -1 #include<stdio.h> #include<string.h> #include<queue> #include<algorithm> using namespace std; const int INF = 0x3f3f3f3f; const int maxn = 1024; const int maxm = 100008; struct EDGE{ int v

A*算法的认识与求第K短路模板

现在来了解A*算法是什么 现在来解决A*求K短路问题 在一个有权图中,从起点到终点最短的路径成为最短路,第2短的路成为次短路,第3短的路成为第3短路,依此类推,第k短的路成为第k短路.那么,第k短路怎么求呢? 对于第k短路,可以想到的一个比较朴素的算法就是广度优先搜索,使用优先队列从源点s进行广搜,当第k次搜索到终点t时,所的长度即所求但是这种方法在运行过程中会产生特别多的状态,当图比较简单.k比较小时,可以一试,但是当k较大或者图中点数较多时,会面临爆栈的危险.目前使用比较多的算法是单源最短路

POJ 2449(求k短路,A*)

题目:求s到t的第k短路. 思路:网上是清一色的A*算法,所以学习了一下.所谓Astar算法其实就是启发式的bfs.这里设置了一个估价函数h,结合当前位置的最短路和到终点的估计最短路长度来选择下一个要扩展的节点(dijkstra算法对于所有的点的h值可以视为是一样的,所以下一个扩展的节点只与当前的最短路g有关).这个h的值越接近手记最短路越好,但是不能少于实际最短路(否则会错误),假设h值就是实际最短路的值,那么这个Astar算法可以一次找到最短路,相对的,如果h比实际值稍大,那么仍然可以去掉很

【POJ】2449 Remmarguts&#39; Date(k短路)

http://poj.org/problem?id=2449 不会.. 百度学习.. 恩. k短路不难理解的. 结合了a_star的思想.每动一次进行一次估价,然后找最小的(此时的最短路)然后累计到k 首先我们建反向边,跑一次从汇到源的最短路,将跑出来的最短路作为估价函数h 根据f=g+h 我们将源s先走,此时实际价值g为0,估价为最短路(他们的和就是s-t的最短路) 将所有s所连的边都做相同的处理,加入到堆中(假设此时到达的点为x,那么x的g等于s到这个点的边权,因为根据最优,g+h此时是从x

【转】K短路

K短路 用dijsktra+A*启发式搜索 当点v第K次出堆的时候,这时候求得的路径是k短路.A*算法有一个启发式函数f(p)=g(p)+h(p), 即评估函数=当前值+当前位置到终点的最短距离g(p):当前从s到p点所走的路径长度,h(p)就是点p到目的点t的最短距离.f(p)就是当前路径从s走到p在从p到t的所走距离.步骤:1>求出h(p).将有向边反向,求出目的点t到所有点的最短距离,用dijkstra算法2>将原点s加入优先队列中3>优先队列取出f(p)最小的一个点p如果p==t

POJ 2449Remmarguts&#39; Date 第K短路

Remmarguts' Date Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 29625   Accepted: 8034 Description "Good man never makes girls wait or breaks an appointment!" said the mandarin duck father. Softly touching his little ducks' head, h