浅谈k短路算法

An Old but Classic Problem

  给定一个$n$个点,$m$条边的带正权有向图。给定$s$和$t$,询问$s$到$t$的所有权和为正路径中,第$k$短的长度。

Notice

  定义两条路径不同,当且仅当它们的边集中存在一条边,使得它只在其中的一条路径上。

Solution#1 Shortest Path & A*

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

  那为什么还要A*呢?我试了试,写了个Dijstra,然后交了一发poj 2449,于是MLE了。。。如果您觉得我的Dijstra太丑了,您可以手写一个交一发。

  所以用A*来优化优化状态数(其实就是玄学优化,需要你的人品还有出题人的素质)

  首先建反图,跑一次最短路算法,得到每个点到$t$的最短路的距离。

  然后用当前走的距离加上到终点的最短路的长度作为优先级进行A*。

  那如何得到答案?

  1. 当一个点第k次出队时,答案是它的优先级
  2. 当终点第k次出队时,答案是它已经走的路程

  据说A*可以被卡成$O\left(nk\log n \right )$,只是我不会QAQ。

Code

  1 /**
  2  * poj
  3  * Problem#2449
  4  * Accepted
  5  * Time: 250ms
  6  * Memory: 9252k
  7  */
  8 #include <iostream>
  9 #include <fstream>
 10 #include <sstream>
 11 #include <algorithm>
 12 #include <cstdio>
 13 #include <cstdlib>
 14 #include <cstring>
 15 #include <ctime>
 16 #include <cctype>
 17 #include <cmath>
 18 #include <vector>
 19 #include <queue>
 20 #include <stack>
 21 #include <map>
 22 #include <set>
 23 #include <bitset>
 24 using namespace std;
 25 typedef bool boolean;
 26
 27 typedef class Edge {
 28     public:
 29         int end;
 30         int next;
 31         int w;
 32
 33         Edge(int end = 0, int next = -1, int w = 0):end(end), next(next), w(w) {        }
 34 }Edge;
 35
 36 const int N = 1e3, M = 1e5;
 37
 38 typedef class MapManager {
 39     public:
 40         int cnt;
 41         int h[N + 5];
 42         Edge edge[M + 5];
 43
 44         MapManager() {        }
 45         MapManager(int n):cnt(-1) {
 46 //            h = new int[(n + 1)];
 47 //            edge = new Edge[(m + 1)];
 48             memset(h, -1, sizeof(int) * (n + 1));
 49         }
 50
 51         inline void addEdge(int u, int v, int w) {
 52             edge[++cnt] = (Edge(v, h[u], w));
 53 //            h[u] = (signed)edge.size() - 1;
 54             h[u] = cnt;
 55         }
 56
 57         inline int start(int node) {    return h[node];        }
 58
 59         Edge& operator [] (int pos) {
 60             return edge[pos];
 61         }
 62 }MapManager;
 63 #define m_endpos -1
 64
 65 int n, m;
 66 MapManager g;
 67 MapManager rg;
 68 int s, t, k;
 69 int ds[N + 5];
 70
 71 inline void init() {
 72     scanf("%d%d", &n, &m);
 73     memset(g.h, -1, sizeof(int) * (n + 1));
 74     memset(rg.h, -1, sizeof(int) * (n + 1));
 75     for(int i = 1, u, v, w; i <= m; i++) {
 76         scanf("%d%d%d", &u, &v, &w);
 77         g.addEdge(u, v, w);
 78         rg.addEdge(v, u, w);
 79     }
 80     scanf("%d%d%d", &s, &t, &k);
 81 //    ds = new int[(n + 1)];
 82 }
 83
 84 #define g rg
 85 #define f ds
 86 #define que que1
 87 boolean vis[N + 5];
 88 queue<int> que;
 89 boolean spfa(int s, int t) {
 90     memset(f, 0x7f, sizeof(int) * (n + 1));
 91     memset(vis, false, sizeof(boolean) * (n + 1));
 92     que.push(s);
 93     f[s] = 0;
 94     while(!que.empty()) {
 95         int e = que.front();
 96         que.pop();
 97         vis[e] = false;
 98         for(int i = g.start(e); i != m_endpos; i = g[i].next) {
 99             int& eu = g[i].end;
100 //            cout << e << " " << eu << " " << i <<endl;
101             if(f[e] + g[i].w < f[eu]) {
102                 f[eu] = f[e] + g[i].w;
103                 if(!vis[eu]) {
104                     que.push(eu);
105                     vis[eu] = true;
106                 }
107             }
108         }
109     }
110     return (f[t] != 0x7f7f7f7f);
111 }
112 #undef g
113 #undef f
114 #undef que
115
116 typedef class Status {
117     public:
118         int node;
119         int dis;
120         int priority;
121
122         Status(int node = 0, int dis = 0):node(node), dis(dis), priority(h()) {        }
123
124         int h() {
125             return dis + ds[node];
126         }
127
128         boolean operator < (Status b) const {
129             return priority > b.priority;
130         }
131 }Status;
132
133 int label[N + 5];
134 priority_queue<Status> que;
135 int bfs(int s, int t) {
136     if(s == t)    k++;
137 //    label = new int[(n + 1)];
138     memset(label, 0, sizeof(int) * (n + 1));
139     que.push(Status(s, 0));
140     while(!que.empty()) {
141         Status e = que.top();
142         que.pop();
143         label[e.node]++;
144         if(e.node == t && label[e.node] == k)
145             return e.dis;
146         for(int i = g.start(e.node); i != m_endpos; i = g[i].next) {
147             if(label[g[i].end] < k)
148                 que.push(Status(g[i].end, e.dis + g[i].w));
149         }
150     }
151     return -1;
152 }
153
154 inline void solve() {
155     if(!spfa(t, s)) {
156         puts("-1");
157         return;
158     }
159     printf("%d", bfs(s, t));
160 }
161
162 int main() {
163     init();
164     solve();
165     return 0;
166 }

最短路算法 + A*

Solution#2  Shortest Path & Protractable Heap

  考虑建立反图,然后跑最短路算法得到以$t$为根的最短路径生成树。

  当走了一条非树边$(u, v, w)$意味着什么?

  最终的路径长度就会因此增加$f[v] - f[u] + w$。

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

  考虑对于一个合法的非树边序列,我们可以找到唯一的一条$s$到$t$的路径与之对应。

  因此,$k$短路的长度就等于第$k$小的代价和加上$s$到$t$的最短路的长度。

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

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

  我们可以通过这样的方法来得到所有的非树边序列。但是我们并不需要所有的非树边序列,因此当找到第$x$短路后再进行拓展状态,然后用优先队列来维护。

  但是这样每次拓展时间复杂度可达$O(m)$,总时间复杂度可以达到$O\left(mk\log \left (mk  \right ) \right )$。

  令人无法接受。但是其中真正会被用到的状态十分少。

  因此可以像UVa 11997那样进行优化。

  当一个非树边序列出队时,代价和比它大的才可能有用。

  因此,考虑一个非树边序列出队时通过下面的方法来进行得到新的序列:

  1. 追加操作:假如最后一条非树边的终点为$v$,找到一条起点在$v$到$t$的路径上代价最小的非树边追加在当前非树边序列后
  2. 替换操作:将最后一条非树边更换为代价比它大的1条非树边。
    例如图中橙色虚线是被替换掉的非树边,紫色是新加入的非树边

  考虑用一些可持久化数据结构(如可持久化斜堆,可持久化线段树,可持久化Treap)来维护起点在点$u$到根的路径上的非树边的代价。

  对于替换操作,

  • 如果用的可持久化堆,那么把最后一条非树边替换为它所在的堆(你从哪个堆把它拿出来的)中它的左右子节点代表的边。
  • 如果用的可持久化平衡树,那么把最后一条非树边直接替换为它的后继
  • ......

  这是一个很稳定的算法,时间复杂度$O\left ( n + m\log m + k\log k \right )$。就是常数有点大,sad.....

  注意一些细节

  • 计算代价时需要考虑终点是否可以到达$t$
  • 考虑$s = t$时,要求的$k$短路包不包含0
  • $k$短路不存在,队首为空

Code

  1 /**
  2  * poj
  3  * Problem#2449
  4  * Accepted
  5  * Time: 438ms
  6  * Memory: 15196k
  7  */
  8 #include <algorithm>
  9 #include <iostream>
 10 #include <cstring>
 11 #include <cstdio>
 12 #include <vector>
 13 #include <queue>
 14 using namespace std;
 15 typedef bool boolean;
 16
 17 #define pii pair<int, int>
 18 #define fi first
 19 #define sc second
 20
 21 typedef class Node {
 22     public:
 23         int val, ed;
 24         Node *l, *r;
 25
 26         Node()    {        }
 27         Node(int val, int ed, Node *l, Node *r):val(val), ed(ed), l(l), r(r) {        }
 28 }Node;
 29
 30 #define Limit 1000000
 31
 32 Node pool[Limit];
 33 Node* top = pool;
 34
 35 Node* newnode(int val, int ed) {
 36     if(top >= pool + Limit)
 37         return new Node(val, ed, NULL, NULL);
 38     top->val = val, top->ed = ed, top->l = top->r = NULL;
 39     return top++;
 40 }
 41
 42 Node* merge(Node* a, Node* b) {
 43     if (!a)    return b;
 44     if (!b)    return a;
 45     if (a->val > b->val)    swap(a, b);
 46     Node* p = newnode(a->val, a->ed);
 47     p->l = a->l, p->r = a->r;
 48     p->r = merge(p->r, b);
 49     swap(p->l, p->r);
 50     return p;
 51 }
 52
 53 typedef class Status {
 54     public:
 55         int dist;
 56         Node* p;
 57
 58         Status(int dist = 0, Node* p = NULL):dist(dist), p(p) {        }
 59
 60         boolean operator < (Status b) const {
 61             return dist > b.dist;
 62         }
 63 }Status;
 64
 65 typedef class Edge {
 66     public:
 67         int end, next, w;
 68
 69         Edge(int end = 0, int next = 0, int w = 0):end(end), next(next), w(w) {        }
 70 }Edge;
 71
 72 typedef class MapManager {
 73     public:
 74         int ce;
 75         int* h;
 76         Edge* es;
 77
 78         MapManager() {            }
 79         MapManager(int n, int m):ce(0) {
 80             h = new int[(n + 1)];
 81             es = new Edge[(m + 5)];
 82             memset(h, 0, sizeof(int) * (n + 1));
 83         }
 84
 85         void addEdge(int u, int v, int w) {
 86             es[++ce] = Edge(v, h[u], w);
 87             h[u] = ce;
 88         }
 89
 90         Edge& operator [] (int pos) {
 91             return es[pos];
 92         }
 93 }MapManager;
 94
 95 int n, m;
 96 int s, t, k;
 97 MapManager g;
 98 MapManager rg;
 99 boolean *vis;
100 int* f, *lase;
101
102 inline void init() {
103     scanf("%d%d", &n, &m);
104     g = MapManager(n, m);
105     rg = MapManager(n, m);
106     for (int i = 1, u, v, w; i <= m; i++) {
107         scanf("%d%d%d", &u, &v, &w);
108         g.addEdge(u, v, w);
109         rg.addEdge(v, u, w);
110     }
111     scanf("%d%d%d", &s, &t, &k);
112 }
113
114 queue<int> que;
115 void spfa(MapManager& g, int s) {
116     vis = new boolean[(n + 1)];
117     f = new int[(n + 1)];
118     lase = new int[(n + 1)];
119     memset(f, 0x7f, sizeof(int) * (n + 1));
120     memset(vis, false, sizeof(boolean) * (n + 1));
121     que.push(s);
122     f[s] = 0, lase[s] = 0;
123     while (!que.empty()) {
124         int e = que.front();
125         que.pop();
126         vis[e] = false;
127         for (int i = g.h[e]; i; i = g[i].next) {
128             int eu = g[i].end, w = g[i].w;
129             if (f[e] + w < f[eu]) {
130                 f[eu] = f[e] + w, lase[eu] = i;
131                 if (!vis[eu]) {
132                     vis[eu] = true;
133                     que.push(eu);
134                 }
135             }
136         }
137     }
138  }
139
140 Node** hs;
141 inline void rebuild() {
142     for (int i = 1; i <= n; i++)
143         for (int j = g.h[i]; j; j = g[j].next) {
144             int e = g[j].end;
145             if (lase[i] != j)
146                 g[j].w += f[e] - f[i];
147         }
148
149     hs = new Node*[(n + 1)];
150     que.push(t);
151     hs[t] = NULL;
152     while (!que.empty()) {
153         int e = que.front();
154         que.pop();
155         if (lase[e])
156             hs[e] = hs[g[lase[e]].end];
157         for (int i = g.h[e]; i; i = g[i].next)
158             if (lase[e] != i && f[g[i].end] != 0x7f7f7f7f)
159                 hs[e] = merge(hs[e], new Node(g[i].w, g[i].end, NULL, NULL));
160         for (int i = rg.h[e]; i; i = rg[i].next) {
161             int eu = rg[i].end;
162             if (lase[eu] == i)
163                 que.push(eu);
164         }
165     }
166 }
167
168 inline int kthpath(int k) {
169     if (s == t)
170         k++;
171     if (f[s] == 0x7f7f7f7f)
172         return -1;
173     if (k == 1)
174         return f[s];
175
176     priority_queue<Status> q;
177     if (!hs[s])
178         return -1;
179
180     q.push(Status(hs[s]->val, hs[s]));
181     while (--k && !q.empty()) {
182         Status e = q.top();
183         q.pop();
184
185         if(k == 1)
186             return e.dist + f[s];
187
188         int eu = e.p->ed;
189         if (hs[eu])
190             q.push(Status(e.dist + hs[eu]->val, hs[eu]));
191         if (e.p->l)
192             q.push(Status(e.dist - e.p->val + e.p->l->val, e.p->l));
193         if (e.p->r)
194             q.push(Status(e.dist - e.p->val + e.p->r->val, e.p->r));
195     }
196     return -1;
197 }
198
199 inline void solve() {
200     printf("%d\n", kthpath(k));
201 }
202
203 int main() {
204     init();
205     spfa(rg, t);
206     rebuild();
207     solve();
208     return 0;
209 }

最短路算法+可持久化堆

特别鸣谢

  YJQ

  ZJC

原文地址:https://www.cnblogs.com/yyf0309/p/Kth_shortest_path.html

时间: 2024-11-10 12:57:55

浅谈k短路算法的相关文章

k短路算法

k短路算法 求解k短路用到了A* 算法,A* ( A star )算法,又称启发式搜索算法,与之相对的,dfs与bfs都成为盲目型搜索:即为带有估价函数的优先队列BFS称为A*算法. 该算法的核心思想为设计一个估价函数,估价函数需要满足下面几个准则: 1:设当前状态state到目标状态所需的估计值为\(f(state)\). 2:在未来的搜索中,实际求出的从当前状态state到目标状态的最小代价为\(g(state)\). 3:对于任意的\(state\),应该有\(f(state)<=g(st

浅谈关于特征选择算法与Relief的实现

一. 背景 1) 问题 在机器学习的实际应用中,特征数量可能较多,其中可能存在不相关的特征,特征之间也可能存在相关性,容易导致如下的后果: 1.     特征个数越多,分析特征.训练模型所需的时间就越长,模型也会越复杂. 2.     特征个数越多,容易引起“维度灾难”,其推广能力会下降. 3.     特征个数越多,容易导致机器学习中经常出现的特征稀疏的问题,导致模型效果下降. 4.     对于模型来说,可能会导致不适定的情况,即是解出的参数会因为样本的微小变化而出现大的波动. 特征选择,能

转:浅谈洗牌算法(面试题)

很多人都有耳闻过洗牌算法,时常会在面试中碰到,我们下面来定义一下这个问题. 所谓洗牌算法,就是给你一个1到n的序列,让你随机打乱,保证每个数出现在任意一个位置的概率相同,也就是说在n!个的排列中,每一个排列出现的概率相同. 最朴素的做法 对于这个问题我们从最朴素的解法谈起.每次随机选出一个没有被选过的数放到一个队列中,如果随机出来的数已经被选过,那么继续随机直到遇到一个没有被选过的数放入到队列中:重复这样子操作直到所有的数都被选择出来. 我们看看这样子作为什么是对的.首先选第一个数的时候有n个数

机器学习 | 浅谈K-近邻算法

K-近邻(KNN)算法是解决分类问题的算法.既可以解决二分类,也可以解决多分类问题. 其实它也可以解决回归问题. K-近邻原理: 某个样本的类别,由与之最相近的K个邻居投票所决定. 例子: 现在有一个样本集,其中所有数据都已经标记好类别,假设有一个未知类别的样本x需要进行分类. 在离这个样本距离最近的K个样本中,统计各个类别的占比.假设k=5时,计算出哪5个样本离未知样本x最近, 然后统计它们的类别,如在这5个样本中,有2个属于类别A,3个属于类别B.由于类别B的占比比较高, 所以得出样本x属于

数据结构杂谈(一)浅谈基本排序算法

0.基本概念 记录:待排序的项目 关键词:决定排序结果 稳定性:相同关键词的记录保持原来的相对次序 1.1插入排序(Insertion Sort) 算法思想 一种简单直观的排序算法,工作原理是通过构建有序序列:对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入. 算法描述 具体算法描述如下: 从第一个元素开始,该元素可以认为已经被排序 取出下一个元素,在已经排序的元素序列中从后向前扫描 如果该元素(已排序)大于新元素,将该元素移到下一位置 重复步骤3,直到找到已排序的元素小于或者等于

浅谈Hex编码算法

一.什么是Hex 将每一个字节表示的十六进制表示的内容,用字符串来显示. 二.作用 将不可见的,复杂的字节数组数据,转换为可显示的字符串数据 类似于Base64编码算法 区别:Base64将三个字节转换为四个字符,Hex将三个字节转换为六个字节 三.应用场景 在XML,JSON等文本中包含不可见数据(二进制数据)时使用 四.使用 1.将字节数组转换为字符串 1 /** 2 * 将字节数组转换为字符串 3 * 一个字节会形成两个字符,最终长度是原始数据的2倍 4 * @param data 5 *

浅谈数据结构-Boyer-Moore算法

上文讲解了KMP算法,这种算法在字符串匹配中应用比较少,在各种文本编辑器中的查找功能大多采用Boyer-Moore算法.1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了这种算法. 算法讲解 开始:假定字符串为"HERE IS A SIMPLE EXAMPLE",搜索词为"EXAMPLE". 1.首先,"字符串"与"搜索词"头部对齐,从尾部开始比较.如果尾部字符不匹配,那么

浅谈对机器学习算法的一些认识(决策树,SVM,knn最近邻,随机森林,朴素贝叶斯、逻辑回归)

一.决策树 定下一个最初的质点,从该点出发.分叉.(由于最初质点有可能落在边界值上,此时有可能会出现过拟合的问题. 二.SVM  svm是除深度学习在深度学习出现之前最好的分类算法了.它的特征如下: (1)它既可应用于线性(回归问题)分类,也可应用于非线性分类; (2)通过调节核函数参数的设置,可将数据集映射到多维平面上,对其细粒度化,从而使它的特征从二维变成多维,将在二维上线性不可分的问题转化为在多维上线性可  分的问题,最后再寻找一个最优切割平面(相当于在决策数基础上再寻找一个最优解),因此

浅谈URLEncoder编码算法

一.为什么要用URLEncoder 客户端在进行网页请求的时候,网址中可能会包含非ASCII码形式的内容,比如中文. 而直接把中文放到网址中请求是不允许的,所以需要用URLEncoder编码地址, 将网址中的非ASCII码内容转换成可以传输的字符 不会被编码的内容 1.大写字母A-Z 2.小写字母a-z 3.数字 0-9 4.标点符 - _ . ! ~ * ' (和 ,) 二.编码原理 1.将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头 eg:  0