ダイクストラ法高速化いろいろ
@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はサイコー

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
    1 const intV = 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 intV = 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 intV = 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)ダイクストラは想定解ではない)
  • 17.
  • 18.
  • 19.
  • 20.
    辺の長さが整数 • 辺の長さが 1のみ → BFSをしろ • 辺の長さが 0 or 1 のみ → 0-1BFSをしろ • 辺の長さが100以下とか→キューを101個用意しろ
  • 21.
  • 22.
    RadixHeapとは • 非負整数専用Heap • 最後に取り出した値より小さな値を入れられない •計算量はならしO(logD) (D: 入れたい値の最大値) • 定数倍がめちゃ軽い • 64bit版も出来ますが, 今回は32bitで説明します
  • 23.
    1 int bsr(uintx) { 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 SearchReverseの略 • 一番左の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(uintx) { 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 10b00001101 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 longlong版を作ればpair<int, int>を入れられ るのでダイクストラに使用できます • unsigned int版を改造してもダイクストラに使用できます →こちら(https://github.com/yosupo06/Algorithm/ blob/master/Cpp/Data%20Structure/RadixHeap.h)
  • 35.