图论 —— k 短路

【概述】

所谓 k 短路问题,是指给定一个具有 n 个点 m 条边的带正权有向图,再给定起点 S 与终点 T,询问从 S 到 T 的所有权和中,第 k 短的路径的长度。

k 短路问题的解决方法有两种,一种是利用最短路算法与A*算法结合求解,另一种是利用最短路算法与可持久化堆结合求解。

【A*算法】

对于 Dijkstra 算法,有一个结论是:当一个点第 k 次出队的时候,此时路径长度就是 s 到它的第 k 短路

但直接来写会造成 MLE,因此要利用 A* 算法来优化状态数。

首先建立反图,跑一次最短路算法,得到每个点到 t 的最短路的距离,然后用当前走带距离加上终点的最短路长度作为优化级来进行 A*

因此,当一个点第 k 次出队时,答案为这个点的优先级,当终点第 k 次出队时,答案为这个点已走的路径

struct Edge {
    int to, next;
    int w;
    Edge() {}
    Edge(int to, int next, int w) : to(to), next(next), w(w) {}
};
struct Map {
    int tot;
    int head[N];
    Edge edge[N * N];
    Map() {
        tot = 0;
        memset(head, -1, sizeof(head));
    }
    void addEdge(int x, int y, int w) {
        edge[++tot].to = y;
        edge[tot].next = head[x];
        edge[tot].w = w;
        head[x] = tot;
    }
    Edge &operator[](int pos) { return edge[pos]; }
};
int n, m;
Map G, GT;
int dis[N];
bool vis[N];
struct Status{
    int node;//点编号
    int diss;//距离
    int priority;//优先级
    Status() : node(0), diss(0), priority(dis[0]) {}
    Status(int node, int diss) : node(node), diss(diss), priority(diss + dis[node]) {}
    bool operator<(Status b) const { return priority > b.priority; }
} status;
bool SPFA(int S, int T, int k) { //对反图求最短路
    memset(dis, INF, sizeof(dis));
    memset(vis, false, sizeof(vis));
    dis[S] = 0;

    queue<int> Q;
    Q.push(S);
    while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        vis[x] = false;
        for (int i = GT.head[x]; i != -1; i = GT[i].next) {
            int y = GT[i].to;
            if (dis[x] + GT[i].w < dis[y]) {
                dis[y] = dis[x] + GT[i].w;
                if (!vis[y]) {
                    Q.push(y);
                    vis[y] = true;
                }
            }
        }
    }
    return dis[T] != INF;
}
int label[N];
int AStart(int S, int T, int k) {
    memset(label, 0, sizeof(label));
    if (S == T)
        k++;

    priority_queue<Status> Q;
    Q.push(Status(S, 0));
    while (!Q.empty()) {
        Status temp = Q.top();
        Q.pop();
        label[temp.node]++;
        if (temp.node == T && label[temp.node] == k)
            return temp.diss;
        for (int i = G.head[temp.node]; i != -1; i = G[i].next)
            if (label[G[i].to] < k)
                Q.push(Status(G[i].to, temp.diss + G[i].w));
    }
    return -1;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y, w;
        scanf("%d%d%d", &x, &y, &w);
        G.addEdge(x, y, w);
        GT.addEdge(y, x, w);
    }

    int s, t, k;
    scanf("%d%d%d", &s, &t, &k);
    if (!SPFA(t, s, k))
        printf("-1\n");
    else
        printf("%d\n", AStart(s, t, k));
    return 0;
}

【可持久化堆】

考虑建立反图,然后跑最短路算法得到以 T 为根的最短路径生成树,当走一条非树边 (u,v) 时,最终的路径长度就会因此增加 dis[v]-dis[u]+w

对于一条路径,我们依次将它经过的非树边记下来,约定得到的序列是这条路径的非树边序列。

考虑对于一个合法的非树边序列,我们可以找到唯一的一条 S 到 T 的路径与之对应,因此,k 短路的长度就等于第 k 小的代价和加上 S 到 T 的最短路长度。

考虑如何来得到一个合法的非树边序列:

  1. 找到一条起点在当前点 p 到根 t 的路径上的非树边
  2. 令 p 等于这条边的终点

我们可以通过这样的方法来得到所有的非树边序列,但是我们并不需要所有的非树边序列,因此当找到第 x 短路后再拓展状态,然后用优先队列来维护,但这样每次拓展时间复杂度可达 O(m),总时间复杂度可以达到 O(mklog⁡(mk))。

这样时间复杂度太高,令人无法接受,因为其中被用到的状态十分少,由于当一个非树边序列出队时,代价和比它大的才可能有用,因此,考虑一个非树边序列出队时通过下面的方法来进行得到新的序列:

  1. 追加操作:假如最后一条非树边的终点为 v,找到一条起点在 v 到 t 的路径上代价最小的非树边追加在当前非树边序列后
  2. 替换操作:将最后一条非树边更换为代价比它大的 1 条非树边

如下图,橙色虚线是被替换掉的非树边,紫色是新加入的非树边

考虑用一些可持久化数据结构来维护起点在点 u 到根的路径上的非树边的代价,对于替换操作,当使用可持久化堆时,把最后一条非树边替换为它所在的堆中它的左右子节点代表的边

struct Node{
    int val,to;
    Node *left,*right;
    Node(){}
    Node(int val, int to, Node *left, Node *right) : val(val), to(to), left(left), right(right) {}
};
#define Limit 1000000
Node pool[Limit];
Node *top = pool;
Node *newNode(int val, int to) {
    if (top >= pool + Limit)
        return new Node(val, to, NULL, NULL);
    top->val = val;
    top->to = to;
    top->left = NULL;
    top->right = NULL;
    return top++;
}
Node *meGTe(Node *a, Node *b) {
    if (!a)
        return b;
    if (!b)
        return a;
    if (a->val > b->val)
        swap(a, b);
    Node *p = newNode(a->val, a->to);
    p->left = a->left;
    p->right = a->right;
    p->right = meGTe(p->right, b);
    swap(p->left, p->right);
    return p;
}
struct Status {
    int dist;
    Node *p;
    Status(){}
    Status(int dist, Node *p) : dist(dist), p(p) {}
    bool operator<(Status b) const { return dist > b.dist; }
};
struct Edge {
    int to, next;
    int w;
    Edge() {}
    Edge(int to, int next, int w) : to(to), next(next), w(w) {}
};
struct Map {
    int tot;
    int *head;
    Edge *edge;

    Map() {}
    Map(int n, int m) : tot(0) {
        head = new int[(n + 1)];
        edge = new Edge[(m + 5)];
        memset(head, 0, sizeof(int) * (n + 1));
    }
    void addEdge(int x, int y, int w) {
        edge[++tot].to = y;
        edge[tot].next = head[x];
        edge[tot].w = w;
        head[x] = tot;
    }
    Edge &operator[](int pos) { return edge[pos]; }
};
int n, m;
int s, t, k;
Map G, GT;
bool *vis;
int *dis, *pre;
queue<int> SPFA(int S) {
    vis = new bool[(n + 1)];
    dis = new int[(n + 1)];
    pre = new int[(n + 1)];

    memset(dis, INF, sizeof(int) * (n + 1));
    memset(vis, false, sizeof(bool) * (n + 1));

    queue<int> Q;
    Q.push(S);
    dis[S] = 0;
    pre[S] = 0;
    while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        vis[x] = false;
        for (int i = GT.head[x]; i; i = GT[i].next) {
            int y = GT[i].to;
            int w = GT[i].w;
            if (dis[x] + w < dis[y]) {
                dis[y] = dis[x] + w;
                pre[y] = i;
                if (!vis[y]) {
                    vis[y] = true;
                    Q.push(y);
                }
            }
        }
    }
    return Q;
}
Node **Hash;
void rebuild(queue<int> Q) { //建堆
    for (int i = 1; i <= n; i++) {
        for (int j = G.head[i]; j; j = G[j].next) {
            int to = G[j].to;
            if (pre[i] != j)
                G[j].w += dis[to] - dis[i];
        }
    }

    Hash = new Node *[(n + 1)];
    Q.push(t);
    Hash[t] = NULL;
    while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        if (pre[x])
            Hash[x] = Hash[G[pre[x]].to];
        for (int i = G.head[x]; i; i = G[i].next)
            if (pre[x] != i && dis[G[i].to] != INF)
                Hash[x] = meGTe(Hash[x], new Node(G[i].w, G[i].to, NULL, NULL));

        for (int i = GT.head[x]; i; i = GT[i].next) {
            int y = GT[i].to;
            if (pre[y] == i)
                Q.push(y);
        }
    }
}
int kthPath(int k) {
    if (s == t)
        k++;
    if (dis[s] == INF)
        return -1;
    if (k == 1)
        return dis[s];

    priority_queue<Status> Q;
    if (!Hash[s])
        return -1;

    Q.push(Status(Hash[s]->val, Hash[s]));
    while (--k && !Q.empty()) {
        Status x = Q.top();
        Q.pop();

        if (k == 1)
            return x.dist + dis[s];

        int y = x.p->to;
        if (Hash[y])
            Q.push(Status(x.dist + Hash[y]->val, Hash[y]));
        if (x.p->left)
            Q.push(Status(x.dist - x.p->val + x.p->left->val, x.p->left));
        if (x.p->right)
            Q.push(Status(x.dist - x.p->val + x.p->right->val, x.p->right));
    }
    return -1;
}
int main() {
    scanf("%d%d", &n, &m);
    G = Map(n, m);
    GT = Map(n, m);
    for (int i = 1, u, v, w; i <= m; i++) {
        scanf("%d%d%d", &u, &v, &w);
        G.addEdge(u, v, w);
        GT.addEdge(v, u, w);
    }
    scanf("%d%d%d", &s, &t, &k);
    queue<int> Q = SPFA(t);
    rebuild(Q);
    printf("%d\n", kthPath(k));
    return 0;
}

【例题】

  • Remmarguts' Date(POJ-2449)(SPFA+A*)点击这里
  • Remmarguts' Date(POJ-2449)(SPFA+可持久化堆)点击这里
发布了1871 篇原创文章 · 获赞 702 · 访问量 194万+

猜你喜欢

转载自blog.csdn.net/u011815404/article/details/102501921
今日推荐