Skip to content

Commit a23b881

Browse files
hitonanodeweb-flow
andauthored
O(1) で chmin() 可能な radix heap (#104)
* array-like radix heap that supports chmin() in O(1) * [auto-verifier] verify commit 578d4c7 * Add radix_heap_array.md Co-authored-by: GitHub <noreply@github.com>
1 parent 8aee141 commit a23b881

File tree

5 files changed

+201
-0
lines changed

5 files changed

+201
-0
lines changed

.verify-helper/timestamps.remote.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"combinatorial_opt/test/mcf_costscaling.yuki1615.test.cpp": "2021-08-18 23:55:14 +0900",
1212
"combinatorial_opt/test/mcf_ns.test.cpp": "2021-07-06 00:01:07 +0900",
1313
"combinatorial_opt/test/mincostflow.radixheap.yuki1288.test.cpp": "2021-09-07 01:07:11 +0900",
14+
"combinatorial_opt/test/mincostflow.radixheaparray.yuki1288.test.cpp": "2021-09-08 01:27:01 +0900",
1415
"combinatorial_opt/test/mincostflow.test.cpp": "2021-09-07 01:07:11 +0900",
1516
"combinatorial_opt/test/mincostflow.yuki1288.test.cpp": "2021-09-07 01:07:11 +0900",
1617
"combinatorial_opt/test/mincostflow.yuki1324.test.cpp": "2021-09-07 01:07:11 +0900",
@@ -42,6 +43,7 @@
4243
"data_structure/test/persistent_queue.test.cpp": "2021-02-26 23:47:50 +0900",
4344
"data_structure/test/queue_operate_all_composite.test.cpp": "2021-06-06 14:54:00 +0900",
4445
"data_structure/test/radix_heap.dijkstra.test.cpp": "2021-09-07 01:07:11 +0900",
46+
"data_structure/test/radix_heap_array.dijkstra.test.cpp": "2021-09-08 01:27:01 +0900",
4547
"data_structure/test/range_kth_smallest_offline.test.cpp": "2021-02-26 23:47:50 +0900",
4648
"data_structure/test/rectange_sum.test.cpp": "2021-02-26 23:57:31 +0900",
4749
"data_structure/test/static_range_inversion.test.cpp": "2021-02-26 23:47:50 +0900",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/1288"
2+
#include "../../data_structure/radix_heap_array.hpp"
3+
#include "../mincostflow_nonegativeloop.hpp"
4+
#include <iostream>
5+
#include <numeric>
6+
#include <string>
7+
#include <vector>
8+
using namespace std;
9+
10+
int main() {
11+
int N;
12+
string S;
13+
cin >> N >> S;
14+
vector<long long> V(N);
15+
for (auto &x : V) cin >> x;
16+
17+
const int s = N * 5, t = s + 1;
18+
MinCostFlow<int, long long> graph(t + 1);
19+
for (int d = 0; d < 5; d++) {
20+
for (int i = 0; i < N - 1; i++) graph.add_edge(d * N + i, d * N + i + 1, N / 4, 0);
21+
}
22+
graph.add_edge(s - 1, 0, N / 4, 0);
23+
24+
for (int i = 0; i < N; i++) {
25+
int b = 0;
26+
if (S[i] == 'u') b = N * 1;
27+
if (S[i] == 'k') b = N * 2;
28+
if (S[i] == 'i') b = N * 3;
29+
int fr = b + i + N, to = b + i;
30+
graph.add_edge(s, fr, 1, 0);
31+
graph.add_edge(fr, to, 1, V[i]);
32+
graph.add_edge(to, t, 1, 0);
33+
}
34+
auto cost = graph.flow<radix_heap_array<unsigned long long>>(s, t, N).second;
35+
cout << accumulate(V.begin(), V.end(), 0LL) - cost << '\n';
36+
}

data_structure/radix_heap_array.hpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
#include <array>
3+
#include <limits>
4+
#include <type_traits>
5+
#include <utility>
6+
#include <vector>
7+
8+
template <class Uint> class radix_heap_array {
9+
int sz;
10+
Uint last;
11+
std::array<std::vector<std::pair<Uint, int>>, std::numeric_limits<Uint>::digits + 1> v;
12+
13+
struct smallpii {
14+
unsigned b : 7;
15+
int j : 25;
16+
};
17+
std::vector<smallpii> i2bj;
18+
19+
template <class U, typename std::enable_if<sizeof(U) == 4>::type* = nullptr>
20+
static inline unsigned bucket(U x) noexcept {
21+
return x ? 32 - __builtin_clz(x) : 0;
22+
}
23+
template <class U, typename std::enable_if<sizeof(U) == 8>::type* = nullptr>
24+
static inline unsigned bucket(U x) noexcept {
25+
return x ? 64 - __builtin_clzll(x) : 0;
26+
}
27+
28+
void pull() {
29+
if (!v[0].empty()) return;
30+
int b = 1;
31+
while (v[b].empty()) ++b;
32+
last = v[b].back().first;
33+
for (int j = 0; j < int(v[b].size()); j++) last = std::min(last, v[b][j].first);
34+
for (int j = 0; j < int(v[b].size()); j++) {
35+
int i = v[b][j].second;
36+
auto bnxt = bucket(v[b][j].first ^ last);
37+
i2bj[i] = {bnxt, int(v[bnxt].size())}, v[bnxt].emplace_back(std::move(v[b][j]));
38+
}
39+
v[b].clear();
40+
}
41+
42+
public:
43+
radix_heap_array() : sz(0), last(0) {}
44+
bool empty() const noexcept { return sz == 0; }
45+
int argmin_pop() {
46+
pull(), --sz;
47+
int i = v[0].back().second;
48+
i2bj[i].j = -1;
49+
v[0].pop_back();
50+
return i;
51+
}
52+
void chmin(Uint vnew, int i) {
53+
if (i >= int(i2bj.size())) i2bj.resize(i + 1, {0, -1});
54+
if (i2bj[i].j < 0) {
55+
auto b = bucket(vnew ^ last);
56+
++sz, i2bj[i] = {b, int(v[b].size())}, v[b].emplace_back(vnew, i);
57+
} else if (v[i2bj[i].b][i2bj[i].j].first > vnew) {
58+
auto bold = i2bj[i].b, bnew = bucket(vnew ^ last);
59+
if (bnew < bold) {
60+
int ilast = v[bold].back().second, j = i2bj[i].j;
61+
std::swap(v[bold][j], v[bold].back());
62+
i2bj[ilast].j = j, i2bj[i] = {bnew, int(v[bnew].size())};
63+
v[bnew].emplace_back(vnew, i), v[bold].pop_back();
64+
} else {
65+
v[bold][i2bj[i].j].first = vnew;
66+
}
67+
}
68+
}
69+
70+
void pop() { argmin_pop(); }
71+
std::pair<Uint, int> top() { return pull(), v[0].back(); }
72+
[[deprecated("NOT usual emplace() opeation!")]] void emplace(Uint vnew, int i) { chmin(vnew, i); }
73+
74+
void clear() noexcept { sz = 0, last = 0, i2bj.clear(); }
75+
};

data_structure/radix_heap_array.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: Array-like radix heap with decrease query(`chmin()` 操作が可能な基数ヒープ)
3+
documentation_of: ./radix_heap_array.hpp
4+
---
5+
6+
`radix_heap.hpp` におけるラベルを非負整数に限り,更にラベル $i$ に対応したキーに対する decrease 操作(いわゆる `chmin`)を $O(1)$ で行えるもの.
7+
8+
## 使用方法
9+
10+
以下のように,Dijkstra 法を効率的に実行可能なクエリが揃っている.以下,$D$ を `UnsignedInt` 型のビット数とする.
11+
12+
- `radix_heap_array<UnsignedInt>()`: Radix heap の初期化.各 $i \ge 0$ について $A_i = \infty$ と初期化.計算量は $O(D)$.
13+
- `bool empty()`: $A\_i < \infty$ である $i$ が存在しないとき,及びそのときに限り `true` を返す.$O(1)$
14+
- `int argmin_pop()`: $A\_i$ が最小値をとる $i$ を一つとって $A\_i = \infty$ と更新し,$i$ を返す.償却 $O(D)$
15+
- `void chmin(UnsignedInt x, int i)`: $A\_i$ を $\min(A\_i, x)$ で更新する.$x$ の値はこれまで `pop()``top()` の対象となった最大の値以上でなければならない.$O(1)$
16+
17+
以下は既存のライブラリとの互換性のために実装されている.
18+
19+
- `void pop()`: $A\_i$ が最小の $i$ の一つについて,$A_i = \infty$ と更新する.償却 $O(D)$
20+
- `pair<UnsignedInt, int> top()`: $A\_i$ が最小の $i$ の一つについて,$(A\_i, i)$ の組を返す.償却 $O(D)$
21+
- `void emplace(Uint vnew, int i)` (非推奨): `chmin()` と同じ効果で,STL における同名の関数とは意味が大きく異なる.既存のライブラリに含まれる `priority_queue` をコンテスト中に素早くこのライブラリに置き換えることを可能にするためにのみ存在している.$O(1)$
22+
23+
Dijkstra 法による最短 $s$ - $t$ パスの計算は次のように書ける:
24+
```cpp
25+
vector<unsigned long long> dist(N, numeric_limits<unsigned long long>::max());
26+
vector<int> prv(N, -1);
27+
dist[s] = 0;
28+
radix_heap_array<unsigned long long> pq;
29+
pq.chmin(0, s);
30+
while (!pq.empty()) {
31+
int now = pq.argmin_pop();
32+
if (now == t) break;
33+
for (const auto &nx : to[now]) {
34+
int nxt = nx.first;
35+
if (dist[nxt] > dist[now] + nx.second) {
36+
dist[nxt] = dist[now] + nx.second;
37+
prv[nxt] = now;
38+
pq.chmin(dist[nxt], nxt);
39+
}
40+
}
41+
}
42+
```
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#define PROBLEM "https://judge.yosupo.jp/problem/shortest_path"
2+
#include "../radix_heap_array.hpp"
3+
#include "../../utilities/reader.hpp"
4+
#include <iostream>
5+
#include <limits>
6+
#include <utility>
7+
#include <vector>
8+
using namespace std;
9+
10+
int main() {
11+
cin.tie(nullptr), ios::sync_with_stdio(false);
12+
int N = rdi(), M = rdi(), s = rdi(), t = rdi();
13+
14+
vector<vector<pair<int, unsigned>>> to(N);
15+
while (M--) {
16+
int a = rdi(), b = rdi(), c = rdi();
17+
to[b].emplace_back(a, c);
18+
}
19+
vector<unsigned long long> dist(N, numeric_limits<unsigned long long>::max());
20+
vector<int> prv(N, -1);
21+
dist[t] = 0;
22+
radix_heap_array<unsigned long long> pq;
23+
pq.chmin(0, t);
24+
while (!pq.empty()) {
25+
int now = pq.argmin_pop();
26+
if (now == s) break;
27+
for (const auto &nx : to[now]) {
28+
int nxt = nx.first;
29+
if (dist[nxt] > dist[now] + nx.second) {
30+
dist[nxt] = dist[now] + nx.second;
31+
prv[nxt] = now;
32+
pq.chmin(dist[nxt], nxt);
33+
}
34+
}
35+
}
36+
if (dist[s] == numeric_limits<unsigned long long>::max()) {
37+
cout << "-1\n";
38+
return 0;
39+
}
40+
41+
std::vector<int> path;
42+
for (int now = s; now != -1; now = prv[now]) path.push_back(now);
43+
44+
cout << dist[s] << ' ' << path.size() - 1 << '\n';
45+
for (int i = 1; i < int(path.size()); i++) cout << path[i - 1] << ' ' << path[i] << '\n';
46+
}

0 commit comments

Comments
 (0)