Your SlideShare is downloading. ×
色々なダイクストラ高速化
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

色々なダイクストラ高速化

0
views

Published on

色々なダイクストラ高速化とありますが結局はRadixHeapの解説です

色々なダイクストラ高速化とありますが結局はRadixHeapの解説です

Published in: Technology

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
0
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. ダイクストラ法高速化いろいろ @yosupot
  • 2. ダイクストラ法とは • 単一始点最短経路を求める有名なアルゴリズム • 普通のヒープを使って計算量はO((V+E)logE) • O(V^2)もありますが今回は触れません
  • 3. ダイクストラを高速化したい!
  • 4. ダイクストラが遅くて困る時の原因
  • 5. ダイクストラが遅くて困る時の原因 1. 解法が間違っている
  • 6. ダイクストラが遅くて困る時の原因 1. 解法が間違っている 2. 他の部分が遅い
  • 7. ダイクストラが遅くて困る時の原因 1. 解法が間違っている 2. 他の部分が遅い 3. ジャッジがPKU
  • 8. ダイクストラは速い!!! 甘えるな!!!!
  • 9. ご静聴 ありがとうございました 参考:http://www.slideshare.net/qnighy/ss-15312828
  • 10. でも本当に高速化はいらない? • 最小費用流とかも速くなる • めちゃ定数倍が厳しいのがあったら役に立つかも • そもそも速度は速いに越したことはない
  • 11. 高速化(基本編)
  • 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. 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. 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. 高速化(基本編) • 単純な枝狩り 非常に有名 • priority_queueに入れる前にその時点での最短距離をチェック • めちゃ効果が高い これを行うと何十倍も速くなる問題も
  • 16. 高速化(基本編) • 単純な枝狩り 非常に有名 • priority_queueに入れる前にその時点での最短距離をチェック • めちゃ効果が高い これを行うと何十倍も速くなる問題も http://abc012.contest.atcoder.jp/tasks/abc012_4 ABC12D 避けられない運命 UTPC2013L 1円ロード http://utpc2013.contest.atcoder.jp/tasks/utpc2013_12 この枝刈りを使わないと厳しい問題もある (そもそもこれはO(VlogE)ダイクストラは想定解ではない)
  • 17. ここからは、いくつか特殊なケースでの 高速化を紹介します
  • 18. 辺の長さが小数(doubleとか)の場合
  • 19. 辺の長さが小数(doubleとか)の場合 このスライドはここで(本当に)終了です。 ご静聴ありがとうございました
  • 20. 辺の長さが整数 • 辺の長さが 1 のみ → BFSをしろ • 辺の長さが 0 or 1 のみ → 0-1BFSをしろ • 辺の長さが100以下とか→キューを101個用意しろ
  • 21. • 辺の長さが大きい場合 • RadixHeapという高速なHeapがあります • このスライドの本題です 辺の長さが整数
  • 22. RadixHeapとは • 非負整数専用Heap • 最後に取り出した値より小さな値を入れられない • 計算量はならしO(logD) (D: 入れたい値の最大値) • 定数倍がめちゃ軽い • 64bit版も出来ますが, 今回は32bitで説明します
  • 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[33]; 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[0].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[0].pop_back(); 32 return last; 33 } 34 }; ソースコード 実装は重くない (skew heapよりは重い)
  • 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. 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. 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[0]に入る • 逆にv[0]にはlastと同じ値しか入れない • v[i+1]の値は必ずv[i]の値より大きい • v[3]とv[4]は12未満の要素しか入れない 1 void push(uint x) { 2 assert(last <= x); 3 sz++; 4 v[bsr(x^last)+1].push_back(x); 5 } →必ず空
  • 27. pop 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 } v[0]に値が入っている場合 v[0]に値が入っていない場合
  • 28. pop 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 } v[0]に値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい v[0]に値が入っていない場合
  • 29. pop 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 } v[0]に値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい v[0]に値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目
  • 30. pop 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 } v[0]に値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい v[0]に値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目 → v[1], v[2], v[3] … と順に中身があるかチェック 中身があったらその中での最小値が全体での最小値
  • 31. pop 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 } v[0]に値が入っている場合 → 中身は必ずlastと同じ値(つまり最小)なので取り出せばいい v[0]に値が入っていない場合 → v[i+1]の値 v[i]の値であることに注目 → v[1], v[2], v[3] … と順に中身があるかチェック 中身があったらその中での最小値が全体での最小値 ただしそのまま取り出すだけではダメ 値の再振り分けが必要
  • 32. pop 引き続きv[0]に値が入っていない場合を考える 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[0], v[1], … v[i-1]は空 → 結局再振り分けするのはv[i]の中身だけでいい! 1 uint pop() { 2 assert(sz); 3 if (!v[0].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[0].pop_back(); 16 return last; 17 }
  • 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[0].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[0].pop_back(); 16 return last; 17 }
  • 34. • unsigned long long版を作ればpair<int, int>を入れられ るのでダイクストラに使用できます • unsigned int版を改造してもダイクストラに使用できます →こちら(https://github.com/yosupo06/Algorithm/ blob/master/Cpp/Data%20Structure/RadixHeap.h)
  • 35. まとめ • そもそもダイクストラの高速化が必要になることは ほとんど無い • それでも速度が欲しかったり、最小費用流を高速化 したかったり、高速なHeapが欲しいなら RadixHeapはサイコー