Más contenido relacionado
La actualidad más candente (20)
Similar a 色々なダイクストラ高速化 (20)
色々なダイクストラ高速化
- 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; こう
- 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個用意しろ
- 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)