Successfully reported this slideshow.                                   Upcoming SlideShare
×

# 色々なダイクストラ高速化

18,750 views

Published on

Published in: Technology
• Full Name
Comment goes here.

Are you sure you want to Yes No Are you sure you want to  Yes  No
• Dating direct: ❤❤❤ http://bit.ly/36cXjBY ❤❤❤

Are you sure you want to  Yes  No

### 色々なダイクストラ高速化

1. 1. ダイクストラ法高速化いろいろ @yosupot
2. 2. ダイクストラ法とは • 単一始点最短経路を求める有名なアルゴリズム • 普通のヒープを使って計算量はO((V+E)logV) • O(V^2)もありますが今回は触れません
3. 3. ダイクストラを高速化したい！
4. 4. ダイクストラが遅くて困る時の原因
5. 5. ダイクストラが遅くて困る時の原因 1. 解法が間違っている
6. 6. ダイクストラが遅くて困る時の原因 1. 解法が間違っている 2. 他の部分が遅い
7. 7. ダイクストラが遅くて困る時の原因 1. 解法が間違っている 2. 他の部分が遅い 3. ジャッジがPKU
8. 8. ダイクストラは速い！！！ 甘えるな！！！！
9. 9. ご静聴 ありがとうございました 参考:http://www.slideshare.net/qnighy/ss-15312828
10. 10. でも本当に高速化はいらない？ • 最小費用流とかも速くなる • めちゃ定数倍が厳しいのがあったら役に立つかも • そもそも速度は速いに越したことはない
11. 11. 高速化(基本編)
12. 12. 1 const int V = 10000, INF = 1<<28; 2 using P = pair<int, int>; 3 vector<P> G[V]; // pair<辺の距離, 行き先の頂点> 4 T dist[V]; // dist[i]はsから頂点iへの最短距離が入る 5 bool used[V]; 6 void dijkstra(int s) { // s:始点 7 fill_n(dist, V, INF); 8 fill_n(used, V, false); 9 priority_queue<P, vector<P>, greater<P>> q; 10 q.push(P(0, s)); 11 while (!q.empty()) { 12 T d; int t;//d:sからの距離 t:行き先 13 tie(d, t) = q.top(); q.pop(); 14 if (used[t]) continue; //もう既に探索済みか 15 used[t] = true; dist[t] = d; 16 for (P e: G[t]) { 17 18 q.push(P(d+e.first, e.second)); 19 } 20 } 21 } 普通のDijkstra
13. 13. 1 const int V = 10000, INF = 1<<28; 2 using P = pair<int, int>; 3 vector<P> G[V]; // pair<辺の距離, 行き先の頂点> 4 T dist[V]; // dist[i]はsから頂点iへの最短距離が入る 5 bool used[V]; 6 void dijkstra(int s) { // s:始点 7 fill_n(dist, V, INF); 8 fill_n(used, V, false); 9 priority_queue<P, vector<P>, greater<P>> q; 10 q.push(P(0, s)); 11 while (!q.empty()) { 12 T d; int t;//d:sからの距離 t:行き先 13 tie(d, t) = q.top(); q.pop(); 14 if (used[t]) continue; //もう既に探索済みか 15 used[t] = true; dist[t] = d; 16 for (P e: G[t]) { 17 18 q.push(P(d+e.first, e.second)); 19 } 20 } 21 } 普通のDijkstra ここに
14. 14. 1 const int V = 10000, INF = 1<<28; 2 using P = pair<int, int>; 3 vector<P> G[V]; // pair<辺の距離, 行き先の頂点> 4 T dist[V]; // dist[i]はsから頂点iへの最短距離が入る 5 bool used[V]; 6 void dijkstra(int s) { // s:始点 7 fill_n(dist, V, INF); 8 fill_n(used, V, false); 9 priority_queue<P, vector<P>, greater<P>> q; 10 q.push(P(0, s)); 11 while (!q.empty()) { 12 T d; int t;//d:sからの距離 t:行き先 13 tie(d, t) = q.top(); q.pop(); 14 if (used[t]) continue; //もう既に探索済みか 15 used[t] = true; dist[t] = d; 16 for (P e: G[t]) { 17 18 q.push(P(d+e.first, e.second)); 19 } 20 } 21 } 普通のDijkstra if (dist[e.second] <= d+e.first) continue; こう
15. 15. 高速化(基本編) • 単純な枝狩り 非常に有名 • priority_queueに入れる前にその時点での最短距離をチェック • めちゃ効果が高い これを行うと何十倍も速くなる問題も
16. 16. 高速化(基本編) • 単純な枝狩り 非常に有名 • priority_queueに入れる前にその時点での最短距離をチェック • めちゃ効果が高い これを行うと何十倍も速くなる問題も http://abc012.contest.atcoder.jp/tasks/abc012_4 ABC12D 避けられない運命 UTPC2013L 1円ロード http://utpc2013.contest.atcoder.jp/tasks/utpc2013_12 この枝刈りを使わないと厳しい問題もある (そもそもこれはO((V+E)logV)ダイクストラは想定解ではない)
17. 17. ここからは、いくつか特殊なケースでの 高速化を紹介します
18. 18. 辺の長さが小数(doubleとか)の場合
19. 19. 辺の長さが小数(doubleとか)の場合 このスライドはここで(本当に)終了です。 ご静聴ありがとうございました
20. 20. 辺の長さが整数 • 辺の長さが 1 のみ → BFSをしろ • 辺の長さが 0 or 1 のみ → 0-1BFSをしろ • 辺の長さが100以下とか→キューを101個用意しろ
21. 21. • 辺の長さが大きい場合 • RadixHeapという高速なHeapがあります • このスライドの本題です 辺の長さが整数
22. 22. RadixHeapとは • 非負整数専用Heap • 最後に取り出した値より小さな値を入れられない • 計算量はならしO(logD) (D: 入れたい値の最大値) • 定数倍がめちゃ軽い • 64bit版も出来ますが, 今回は32bitで説明します
23. 23. 1 int bsr(uint x) { 2 if (x == 0) return -1; 3 return 31-__builtin_clz(x); 4 } 5 6 struct RadixHeapInt { 7 vector<uint> v; 8 uint last, sz; 9 RadixHeapInt() { 10 last = sz = 0; 11 } 12 void push(uint x) { 13 assert(last <= x); 14 sz++; 15 v[bsr(x^last)+1].push_back(x); 16 } 17 uint pop() { 18 assert(sz); 19 if (!v.size()) { 20 int i = 1; 21 while (!v[i].size()) i++; 22 uint new_last = 23 *min_element(v[i].begin(), v[i].end()); 24 for (uint x: v[i]) { 25 v[bsr(x^new_last)+1].push_back(x); 26 } 27 last = new_last; 28 v[i].clear(); 29 } 30 sz--; 31 v.pop_back(); 32 return last; 33 } 34 }; ソースコード 実装は重くない (skew heapよりは重い)
24. 24. • Bit Search Reverseの略 • 一番左の1のbitが(0-indexedで)何番目かを数える • ロバストなlog2(x)とも考えられる • __builtin_clz(x)は31-bsr(x)を返してくれる便利関数 1 int bsr(uint x) { 2 if (x == 0) return -1; 3 return 31-__builtin_clz(x); 4 } bsrとは？ • 0b00010000 -> 4 • 0b01011001 -> 6 • 0b11111111 -> 0 • 0b00000000 -> -1 (builtin_clzに0を渡すとぶっ壊れるため場合分け)
25. 25. push 1 void push(uint x) { 2 assert(last <= x); 3 sz++; 4 v[bsr(x^last)+1].push_back(x); 5 } • RadixHeapでは値がpushされるとbsr(x^last)+1によって33 個のバッファに振り分けられる • 逆にd個目のバッファの中の値xについてd==bsr(x^last)+1 というのはいつでも(pushした後もpopした後も)33個の バッファの中の全ての値について成立していなければいけな い • push関数自体はv[bsr(x^last)+1]に値を放り込むだけでいい
26. 26. push v[i] 中身(last=12) 0 0b00001100 1 0b00001101 2 0b0000111x 3 該当なし 4 該当なし 5 0b0001xxxx 6 0b001xxxxx 7 0b01xxxxxx 8 0b1xxxxxxx 右はlast=12(0b00001100)の時の例 重要な性質 • lastに関わらずlastと同じ値はvに入る • 逆にvにはlastと同じ値しか入れない • v[i+1]の値は必ずv[i]の値より大きい • vとvは12未満の要素しか入れない 1 void push(uint x) { 2 assert(last <= x); 3 sz++; 4 v[bsr(x^last)+1].push_back(x); 5 } →必ず空
27. 27. pop 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 } vに値が入っている場合 vに値が入っていない場合
28. 28. pop 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 } vに値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい vに値が入っていない場合
29. 29. pop 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 } vに値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい vに値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目
30. 30. pop 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 } vに値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい vに値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目 → v, v, v … と順に中身があるかチェック 中身があったらその中での最小値が全体での最小値
31. 31. pop 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 } vに値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい vに値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目 → v, v, v … と順に中身があるかチェック 中身があったらその中での最小値が全体での最小値 ただしそのまま取り出すだけではダメ 値の再振り分けが必要
32. 32. pop 引き続きvに値が入っていない場合を考える v[i]から新しく最小値new_lastを取り出したとする → i, i+1, i+2 … bit目はlastとnew_lastで変わらない → v[i+1], v[i+2], v[i+3] … に入る値の範囲は変わらない！ → bsr(last^new_last) は当然 i-1 更に、v[i]から新しく取り出したならv, v, … v[i-1]は空 → 結局再振り分けするのはv[i]の中身だけでいい！ 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 }
33. 33. pop そして、v[i]の値とnew_lastはi-1, i, i+1, … bit目は等しい → 再振り分けされたv[i]の値は必ずv[j](j < i)へ行く (i, i+1, i+2 … bit目はlastとnew_lastで変わらない事とnew_lastはv[i]に属すことから) → 一つの値について、それが再振り分けされる回数は必ず32回以内 → ならし計算量がO(logD)になることが保証される 1 uint pop() { 2 assert(sz); 3 if (!v.size()) { 4 int i = 1; 5 while (!v[i].size()) i++; 6 uint new_last = 7 *min_element(v[i].begin(), v[i].end()); 8 for (uint x: v[i]) { 9 v[bsr(x^new_last)+1].push_back(x); 10 } 11 last = new_last; 12 v[i].clear(); 13 } 14 sz--; 15 v.pop_back(); 16 return last; 17 }
34. 34. • unsigned long long版を作ればpair<int, int>を入れられ るのでダイクストラに使用できます • unsigned int版を改造してもダイクストラに使用できます →こちら(https://github.com/yosupo06/Algorithm/ blob/master/Cpp/Data%20Structure/RadixHeap.h)
35. 35. まとめ • そもそもダイクストラの高速化が必要になることは ほとんど無い • それでも速度が欲しかったり、最小費用流を高速化 したかったり、高速なHeapが欲しいなら RadixHeapはサイコー