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*。
那如何得到答案?
- 當一個點第k次出隊時,答案是它的優先級
- 當終點第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 }
Solution#2 Shortest Path & Protractable Heap
考慮建立反圖,然后跑最短路算法得到以$t$為根的最短路徑生成樹。
當走了一條非樹邊$(u, v, w)$意味着什么?

最終的路徑長度就會因此增加$f[v] - f[u] + w$。
對於一條路徑,我們依次將它經過的非樹邊記下來,約定得到的序列是這條路徑的非樹邊序列。
考慮對於一個合法的非樹邊序列,我們可以找到唯一的一條$s$到$t$的路徑與之對應。
因此,$k$短路的長度就等於第$k$小的代價和加上$s$到$t$的最短路的長度。
考慮如何來得到一個合法的非樹邊序列。
- 找到一條起點在當前點$p$到根$t$的路徑上的非樹邊
- 令p等於這條邊的終點。
我們可以通過這樣的方法來得到所有的非樹邊序列。但是我們並不需要所有的非樹邊序列,因此當找到第$x$短路后再進行拓展狀態,然后用優先隊列來維護。
但是這樣每次拓展時間復雜度可達$O(m)$,總時間復雜度可以達到$O\left(mk\log \left (mk \right ) \right )$。
令人無法接受。但是其中真正會被用到的狀態十分少。
因此可以像UVa 11997那樣進行優化。
當一個非樹邊序列出隊時,代價和比它大的才可能有用。
因此,考慮一個非樹邊序列出隊時通過下面的方法來進行得到新的序列:
- 追加操作:假如最后一條非樹邊的終點為$v$,找到一條起點在$v$到$t$的路徑上代價最小的非樹邊追加在當前非樹邊序列后
- 替換操作:將最后一條非樹邊更換為代價比它大的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
