SlideShare une entreprise Scribd logo
1  sur  35
Télécharger pour lire hors ligne
ダイクストラ法高速化いろいろ
@yosupot
ダイクストラ法とは
• 単一始点最短経路を求める有名なアルゴリズム
• 普通のヒープを使って計算量はO((V+E)logV)
• O(V^2)もありますが今回は触れません
ダイクストラを高速化したい!
ダイクストラが遅くて困る時の原因
ダイクストラが遅くて困る時の原因
1. 解法が間違っている
ダイクストラが遅くて困る時の原因
1. 解法が間違っている
2. 他の部分が遅い
ダイクストラが遅くて困る時の原因
1. 解法が間違っている
2. 他の部分が遅い
3. ジャッジがPKU
ダイクストラは速い!!!
甘えるな!!!!
ご静聴
ありがとうございました
参考:http://www.slideshare.net/qnighy/ss-15312828
でも本当に高速化はいらない?
• 最小費用流とかも速くなる
• めちゃ定数倍が厳しいのがあったら役に立つかも
• そもそも速度は速いに越したことはない
高速化(基本編)
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
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
ここに
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; こう
高速化(基本編)
• 単純な枝狩り 非常に有名
• priority_queueに入れる前にその時点での最短距離をチェック
• めちゃ効果が高い これを行うと何十倍も速くなる問題も
高速化(基本編)
• 単純な枝狩り 非常に有名
• 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)ダイクストラは想定解ではない)
ここからは、いくつか特殊なケースでの
高速化を紹介します
辺の長さが小数(doubleとか)の場合
辺の長さが小数(doubleとか)の場合
このスライドはここで(本当に)終了です。
ご静聴ありがとうございました
辺の長さが整数
• 辺の長さが 1 のみ → BFSをしろ
• 辺の長さが 0 or 1 のみ → 0-1BFSをしろ
• 辺の長さが100以下とか→キューを101個用意しろ
• 辺の長さが大きい場合
• RadixHeapという高速なHeapがあります
• このスライドの本題です
辺の長さが整数
RadixHeapとは
• 非負整数専用Heap
• 最後に取り出した値より小さな値を入れられない
• 計算量はならしO(logD) (D: 入れたい値の最大値)
• 定数倍がめちゃ軽い
• 64bit版も出来ますが, 今回は32bitで説明します
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よりは重い)
• 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を渡すとぶっ壊れるため場合分け)
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]に値を放り込むだけでいい
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 }
→必ず空
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]に値が入っていない場合
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]に値が入っていない場合
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]の値であることに注目
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] … と順に中身があるかチェック
中身があったらその中での最小値が全体での最小値
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] … と順に中身があるかチェック
中身があったらその中での最小値が全体での最小値
ただしそのまま取り出すだけではダメ 値の再振り分けが必要
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 }
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 }
• unsigned long long版を作ればpair<int, int>を入れられ
るのでダイクストラに使用できます
• unsigned int版を改造してもダイクストラに使用できます
→こちら(https://github.com/yosupo06/Algorithm/
blob/master/Cpp/Data%20Structure/RadixHeap.h)
まとめ
• そもそもダイクストラの高速化が必要になることは
ほとんど無い
• それでも速度が欲しかったり、最小費用流を高速化
したかったり、高速なHeapが欲しいなら
RadixHeapはサイコー

Contenu connexe

Tendances

プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~Takuya Akiba
 
様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題tmaehara
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方Kensuke Otsuki
 
平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズムTakuya Akiba
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)AtCoder Inc.
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性Hibiki Yamashiro
 
充足可能性問題のいろいろ
充足可能性問題のいろいろ充足可能性問題のいろいろ
充足可能性問題のいろいろHiroshi Yamashita
 

Tendances (20)

プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
 
直交領域探索
直交領域探索直交領域探索
直交領域探索
 
Rolling hash
Rolling hashRolling hash
Rolling hash
 
様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
 
グラフネットワーク〜フロー&カット〜
グラフネットワーク〜フロー&カット〜グラフネットワーク〜フロー&カット〜
グラフネットワーク〜フロー&カット〜
 
平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)
 
明日使えないすごいビット演算
明日使えないすごいビット演算明日使えないすごいビット演算
明日使えないすごいビット演算
 
最大流 (max flow)
最大流 (max flow)最大流 (max flow)
最大流 (max flow)
 
全域木いろいろ
全域木いろいろ全域木いろいろ
全域木いろいろ
 
abc032
abc032abc032
abc032
 
Convex Hull Trick
Convex Hull TrickConvex Hull Trick
Convex Hull Trick
 
グラフと木
グラフと木グラフと木
グラフと木
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性
 
充足可能性問題のいろいろ
充足可能性問題のいろいろ充足可能性問題のいろいろ
充足可能性問題のいろいろ
 
一般グラフの最大マッチング
一般グラフの最大マッチング一般グラフの最大マッチング
一般グラフの最大マッチング
 
Binary indexed tree
Binary indexed treeBinary indexed tree
Binary indexed tree
 
Chokudai search
Chokudai searchChokudai search
Chokudai search
 
目指せグラフマスター
目指せグラフマスター目指せグラフマスター
目指せグラフマスター
 

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

冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safeKumazaki Hiroki
 
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara onlineHisatoshi Onishi
 
Material
MaterialMaterial
Material_TUNE_
 
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)mametter
 
虫食算を作るアルゴリズム 公表Ver
虫食算を作るアルゴリズム 公表Ver虫食算を作るアルゴリズム 公表Ver
虫食算を作るアルゴリズム 公表VerKensuke Otsuki
 
マルチコアを用いた画像処理
マルチコアを用いた画像処理マルチコアを用いた画像処理
マルチコアを用いた画像処理Norishige Fukushima
 
USP友の会勉強会、状態遷移図の巻
USP友の会勉強会、状態遷移図の巻USP友の会勉強会、状態遷移図の巻
USP友の会勉強会、状態遷移図の巻Ryuichi Ueda
 
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & WalkthroughIsaac Mathis
 
ICFP2009-いかにして我々は戦ったか
ICFP2009-いかにして我々は戦ったかICFP2009-いかにして我々は戦ったか
ICFP2009-いかにして我々は戦ったかina job
 
Kink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageKink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageTaku Miyakawa
 
㉑CSSでアニメーション!その2
㉑CSSでアニメーション!その2㉑CSSでアニメーション!その2
㉑CSSでアニメーション!その2Nishida Kansuke
 
Arduinoでプログラミングに触れてみよう 続編
Arduinoでプログラミングに触れてみよう 続編Arduinoでプログラミングに触れてみよう 続編
Arduinoでプログラミングに触れてみよう 続編Hiromu Yakura
 
純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門Kimikazu Kato
 
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)AtCoder Inc.
 

Similaire à 色々なダイクストラ高速化 (20)

WUPC2012
WUPC2012WUPC2012
WUPC2012
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
 
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online
「はじめよう、シェル芸」オープンキャンプin南島原2020/OpenCamp in Minami-shimabara online
 
Jpoug 20120721
Jpoug 20120721Jpoug 20120721
Jpoug 20120721
 
SVM -R-
SVM -R-SVM -R-
SVM -R-
 
Material
MaterialMaterial
Material
 
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)
Ruby を用いた超絶技巧プログラミング(夏のプログラミングシンポジウム 2012)
 
虫食算を作るアルゴリズム 公表Ver
虫食算を作るアルゴリズム 公表Ver虫食算を作るアルゴリズム 公表Ver
虫食算を作るアルゴリズム 公表Ver
 
マルチコアを用いた画像処理
マルチコアを用いた画像処理マルチコアを用いた画像処理
マルチコアを用いた画像処理
 
USP友の会勉強会、状態遷移図の巻
USP友の会勉強会、状態遷移図の巻USP友の会勉強会、状態遷移図の巻
USP友の会勉強会、状態遷移図の巻
 
HPC Phys-20201203
HPC Phys-20201203HPC Phys-20201203
HPC Phys-20201203
 
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough
“Stay Right”ペンテスト体験:SANS Holiday Hack Challenge 2017の解説 & Walkthrough
 
ICFP2009-いかにして我々は戦ったか
ICFP2009-いかにして我々は戦ったかICFP2009-いかにして我々は戦ったか
ICFP2009-いかにして我々は戦ったか
 
Kink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageKink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based language
 
㉑CSSでアニメーション!その2
㉑CSSでアニメーション!その2㉑CSSでアニメーション!その2
㉑CSSでアニメーション!その2
 
Arduinoでプログラミングに触れてみよう 続編
Arduinoでプログラミングに触れてみよう 続編Arduinoでプログラミングに触れてみよう 続編
Arduinoでプログラミングに触れてみよう 続編
 
純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門
 
Gorinphp0729
Gorinphp0729Gorinphp0729
Gorinphp0729
 
Gorinphp0729
Gorinphp0729Gorinphp0729
Gorinphp0729
 
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第四回講義資料(ワークスアプリケーションズ & AtCoder)
 

Dernier

論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A surveyToru Tamaki
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものですiPride Co., Ltd.
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNetToru Tamaki
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...Toru Tamaki
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdftaisei2219
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Yuma Ohgami
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 

Dernier (9)

論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものです
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdf
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 

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

  • 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((V+E)logV)ダイクストラは想定解ではない)
  • 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)