16 static constexpr std::uint32_t
unreachable = std::numeric_limits<std::uint32_t>::max();
19 : g_(g), source_(source), deletion_fallback_fraction_(deletion_fallback_fraction) {
23 [[nodiscard]]
const std::vector<std::uint32_t>&
distances() const noexcept {
return dist_; }
24 [[nodiscard]] std::size_t
reachable_count() const noexcept {
return reachable_count_; }
30 last_deletion_candidates_ = 0;
31 last_affected_vertices_ = 0;
32 last_used_full_recompute_ =
false;
33 if (batch.
empty())
return;
36 prepare_batch_workspace(batch.
updates.size());
38 for (
auto it = batch.
updates.rbegin(); it != batch.
updates.rend(); ++it) {
42 const auto key = edge_key(u, v);
43 if (!seen_final_updates_.insert(key))
continue;
45 final_additions_.emplace_back(it->src, it->dst);
47 final_deletions_.emplace_back(it->src, it->dst);
48 final_deletion_keys_.insert(key);
52 deletion_candidates_.reserve(final_deletions_.size() * (
is_directed(g_) ? 1 : 2));
53 existing_deletions_.reserve(final_deletions_.size());
54 for (
const auto& [u, v] : final_deletions_) {
56 existing_deletions_.emplace_back(u, v);
57 if (is_shortest_parent(u, v)) deletion_candidates_.push_back(v);
58 if (!
is_directed(g_) && is_shortest_parent(v, u)) deletion_candidates_.push_back(u);
60 std::sort(deletion_candidates_.begin(), deletion_candidates_.end());
61 deletion_candidates_.erase(std::unique(deletion_candidates_.begin(), deletion_candidates_.end()),
62 deletion_candidates_.end());
63 last_deletion_candidates_ = deletion_candidates_.size();
65 affected_vertices_.reserve(std::min<std::size_t>(deletion_candidates_.size() * 2 + 8,
67 bool fallback_needed =
false;
68 if (!deletion_candidates_.empty()) {
69 fallback_needed = !compute_affected_prebatch(existing_deletions_, final_deletion_keys_);
76 if (fallback_needed) {
79 last_used_full_recompute_ =
true;
83 if (!affected_vertices_.empty()) {
84 old_affected_dist_.reserve(affected_vertices_.size());
85 for (
auto v : affected_vertices_) {
86 old_affected_dist_.push_back(v < dist_.size() ? dist_[v] :
unreachable);
92 for (
const auto& [u, v] : final_additions_) {
93 relax_edge(u, v, bfs_queue_);
96 propagate_decreases(bfs_queue_);
102 reachable_count_ = 0;
106 bfs_queue_.reserve(std::max(bfs_queue_.capacity(),
vertex_count(g_)));
108 bfs_queue_.push_back(source_);
109 std::size_t head = 0;
110 while (head < bfs_queue_.size()) {
111 const auto u = bfs_queue_[head++];
114 dist_[v] = dist_[u] + 1;
115 bfs_queue_.push_back(v);
119 reachable_count_ = bfs_queue_.size();
123 class ReusableKeySet {
125 void reset(std::size_t expected_entries) {
126 std::size_t required = 8;
127 const auto target = expected_entries * 2 + 1;
128 while (required < target) required <<= 1;
129 if (keys_.size() < required) {
130 keys_.assign(required, 0);
131 stamps_.assign(required, 0);
132 mask_ = required - 1;
137 if (generation_ == 0) {
138 std::fill(stamps_.begin(), stamps_.end(), 0);
143 bool insert(std::uint64_t key)
noexcept {
144 std::size_t slot =
static_cast<std::size_t
>(mix(key)) & mask_;
145 while (stamps_[slot] == generation_) {
146 if (keys_[slot] == key)
return false;
147 slot = (slot + 1) & mask_;
150 stamps_[slot] = generation_;
154 [[nodiscard]]
bool contains(std::uint64_t key)
const noexcept {
155 if (keys_.empty())
return false;
156 std::size_t slot =
static_cast<std::size_t
>(mix(key)) & mask_;
157 while (stamps_[slot] == generation_) {
158 if (keys_[slot] == key)
return true;
159 slot = (slot + 1) & mask_;
165 static std::uint64_t mix(std::uint64_t value)
noexcept {
166 value += 0x9e3779b97f4a7c15ULL;
167 value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ULL;
168 value = (value ^ (value >> 27)) * 0x94d049bb133111ebULL;
169 return value ^ (value >> 31);
172 std::vector<std::uint64_t> keys_;
173 std::vector<std::uint32_t> stamps_;
174 std::size_t mask_{0};
175 std::uint32_t generation_{0};
178 [[nodiscard]] std::uint64_t edge_key(
VertexId u,
VertexId v)
const noexcept {
180 return (
static_cast<std::uint64_t
>(u) << 32) |
static_cast<std::uint64_t
>(v);
183 void ensure_workspace(std::size_t vertices) {
184 if (affected_.size() < vertices) affected_.resize(vertices, 0);
185 if (lost_parent_count_.size() < vertices) lost_parent_count_.resize(vertices, 0);
186 if (shortest_parent_count_.size() < vertices) shortest_parent_count_.resize(vertices, 0);
189 void prepare_batch_workspace(std::size_t operations) {
190 seen_final_updates_.reset(operations);
191 final_deletion_keys_.reset(operations);
192 final_additions_.clear();
193 final_deletions_.clear();
194 existing_deletions_.clear();
195 deletion_candidates_.clear();
196 affected_vertices_.clear();
197 old_affected_dist_.clear();
199 touched_loss_vertices_.clear();
201 if (final_additions_.capacity() < operations) final_additions_.reserve(operations);
202 if (final_deletions_.capacity() < operations) final_deletions_.reserve(operations);
203 if (existing_deletions_.capacity() < operations) existing_deletions_.reserve(operations);
206 [[nodiscard]]
bool is_shortest_parent(
VertexId u,
VertexId v)
const noexcept {
207 return u < dist_.size() && v < dist_.size() && dist_[u] !=
unreachable &&
208 dist_[v] !=
unreachable && dist_[u] + 1 == dist_[v];
211 [[nodiscard]] std::uint32_t shortest_parent_count(
VertexId v) {
212 if (v >= dist_.size() || v == source_ || dist_[v] ==
unreachable)
return 0;
213 if (shortest_parent_count_[v] != 0)
return shortest_parent_count_[v];
214 std::uint32_t count = 0;
216 if (is_shortest_parent(p, v)) ++count;
218 shortest_parent_count_[v] = count;
222 bool compute_affected_prebatch(
223 const std::vector<std::pair<VertexId, VertexId>>& existing_deletions,
224 const ReusableKeySet& final_deletion_keys) {
225 const auto fallback_limit = std::max<std::size_t>(
226 1,
static_cast<std::size_t
>(
static_cast<double>(
vertex_count(g_)) * deletion_fallback_fraction_));
227 invalidate_.reserve(std::min<std::size_t>(existing_deletions.size() * 2 + 8,
vertex_count(g_)));
229 auto record_parent_loss = [&](
VertexId v) {
230 if (v >= dist_.size() || v == source_ || dist_[v] ==
unreachable || affected_[v])
return;
231 if (lost_parent_count_[v] == 0) touched_loss_vertices_.push_back(v);
232 ++lost_parent_count_[v];
233 const auto support = shortest_parent_count(v);
234 if (support != 0 && lost_parent_count_[v] >= support) {
236 affected_vertices_.push_back(v);
237 invalidate_.push_back(v);
238 ++last_affected_vertices_;
242 for (
const auto& [u, v] : existing_deletions) {
243 if (is_shortest_parent(u, v)) record_parent_loss(v);
244 if (!
is_directed(g_) && is_shortest_parent(v, u)) record_parent_loss(u);
247 std::size_t head = 0;
248 while (head < invalidate_.size()) {
249 if (last_affected_vertices_ > fallback_limit)
return false;
250 const auto u = invalidate_[head++];
251 if (u >= dist_.size() || dist_[u] ==
unreachable)
continue;
253 if (v >= dist_.size() || dist_[v] != dist_[u] + 1)
return;
254 if (final_deletion_keys.contains(edge_key(u, v)))
return;
255 record_parent_loss(v);
258 return last_affected_vertices_ <= fallback_limit;
261 [[nodiscard]] std::uint32_t best_boundary_distance(
VertexId v)
const {
264 if (p >= dist_.size() || p >= affected_.size() || affected_[p] || dist_[p] ==
unreachable)
return;
265 const auto candidate = dist_[p] + 1;
266 if (candidate < best) best = candidate;
271 void repair_affected() {
272 for (
auto v : affected_vertices_) {
279 using Item = std::pair<std::uint32_t, VertexId>;
280 const auto compare = std::greater<Item>{};
281 repair_heap_.clear();
282 if (repair_heap_.capacity() < affected_vertices_.size()) {
283 repair_heap_.reserve(affected_vertices_.size());
285 for (
auto v : affected_vertices_) {
286 const auto best = best_boundary_distance(v);
290 repair_heap_.emplace_back(best, v);
291 std::push_heap(repair_heap_.begin(), repair_heap_.end(), compare);
295 while (!repair_heap_.empty()) {
296 std::pop_heap(repair_heap_.begin(), repair_heap_.end(), compare);
297 const auto [du, u] = repair_heap_.back();
298 repair_heap_.pop_back();
299 if (u >= dist_.size() || du != dist_[u])
continue;
301 if (v >= affected_.size() || !affected_[v])
return;
302 const auto candidate = du + 1;
303 if (candidate < dist_[v]) {
305 dist_[v] = candidate;
306 repair_heap_.emplace_back(candidate, v);
307 std::push_heap(repair_heap_.begin(), repair_heap_.end(), compare);
313 for (std::size_t i = 0; i < affected_vertices_.size(); ++i) {
314 const auto v = affected_vertices_[i];
315 const auto old_dist = i < old_affected_dist_.size() ? old_affected_dist_[i] :
unreachable;
316 if (v < dist_.size() && dist_[v] !=
unreachable && dist_[v] < old_dist) bfs_queue_.push_back(v);
318 propagate_decreases(bfs_queue_);
321 void clear_workspace() {
322 for (
auto v : affected_vertices_) {
323 if (v < affected_.size()) affected_[v] = 0;
325 for (
auto v : touched_loss_vertices_) {
326 if (v < lost_parent_count_.size()) lost_parent_count_[v] = 0;
327 if (v < shortest_parent_count_.size()) shortest_parent_count_[v] = 0;
329 touched_loss_vertices_.clear();
333 if (u >= dist_.size() || v >= dist_.size() || dist_[u] ==
unreachable)
return;
334 if (dist_[u] + 1 < dist_[v]) {
336 dist_[v] = dist_[u] + 1;
341 void propagate_decreases(std::vector<VertexId>& q) {
342 std::size_t head = 0;
343 while (head < q.size()) {
344 const auto u = q[head++];
347 if (dist_[u] + 1 < dist_[v]) {
349 dist_[v] = dist_[u] + 1;
358 double deletion_fallback_fraction_{0.35};
359 std::vector<std::uint32_t> dist_;
360 std::size_t reachable_count_{0};
361 std::vector<std::uint8_t> affected_;
362 std::vector<std::uint32_t> lost_parent_count_;
363 std::vector<std::uint32_t> shortest_parent_count_;
364 std::vector<VertexId> touched_loss_vertices_;
366 ReusableKeySet seen_final_updates_;
367 ReusableKeySet final_deletion_keys_;
368 std::vector<std::pair<VertexId, VertexId>> final_additions_;
369 std::vector<std::pair<VertexId, VertexId>> final_deletions_;
370 std::vector<std::pair<VertexId, VertexId>> existing_deletions_;
371 std::vector<VertexId> deletion_candidates_;
372 std::vector<VertexId> affected_vertices_;
373 std::vector<std::uint32_t> old_affected_dist_;
374 std::vector<VertexId> invalidate_;
375 std::vector<VertexId> bfs_queue_;
376 std::vector<std::pair<std::uint32_t, VertexId>> repair_heap_;
378 std::size_t last_deletion_candidates_{0};
379 std::size_t last_affected_vertices_{0};
380 bool last_used_full_recompute_{
false};