m1une's library

This documentation is automatically generated by online-judge-tools/verification-helper

View on GitHub

:heavy_check_mark: Rollback DSU
(ds/dsu/rollback_dsu.hpp)

Overview

RollbackDsu maintains a partition under merges and can restore any earlier snapshot on the current history path. It is useful for divide-and-conquer over time, segment trees over query intervals, version trees, and offline dynamic connectivity.

Union by size keeps each parent chain logarithmic. Path compression is intentionally omitted because it would mutate an unbounded number of entries that then need to be logged.

Every merge call appends exactly one history entry. A redundant merge appends a no-op entry and returns false, so one call to undo() always reverses one call to merge().

API

struct RollbackDsu {
    RollbackDsu();
    explicit RollbackDsu(int n);

    int size() const;
    bool empty() const;
    int component_count() const;
    int history_size() const;
    void reserve_history(int count);

    int leader(int vertex) const;
    bool same(int first, int second) const;
    int group_size(int vertex) const;
    int size(int vertex) const;
    bool merge(int first, int second);

    bool undo();
    int snapshot() const;
    void rollback(int state);
    std::vector<std::vector<int>> groups() const;
};
Method Description Complexity
RollbackDsu() Creates an empty DSU. $O(1)$
RollbackDsu(n) Creates n singleton sets. $O(N)$
size() Returns the number of elements. $O(1)$
empty() Returns whether there are no elements. $O(1)$
component_count() Returns the current number of sets. $O(1)$
history_size() Returns the number of recorded merge calls. $O(1)$
reserve_history(count) Reserves history capacity. $O(H)$ if reallocation occurs
leader(v) Returns the representative containing v. $O(\log N)$
same(u, v) Returns whether u and v are connected. $O(\log N)$
group_size(v), size(v) Returns the size of the set containing v. $O(\log N)$
merge(u, v) Records one operation and merges the sets if distinct; returns whether the partition changed. $O(\log N)$ amortized for history growth
undo() Reverses the most recent merge call; returns false when history is empty. $O(1)$
snapshot() Returns the current history position. $O(1)$
rollback(state) Restores a previously returned snapshot on the current history path. $O(H - state)$
groups() Materializes every current set. $O(N \log N)$

Indices must lie in [0, size()). A rollback state must lie between 0 and the current history_size(). Rolling forward to a discarded state is not supported. Query methods are const and never change the represented state.

The DSU stores $O(N + H)$ integers, where H is the current history length.

Example

#include "ds/dsu/rollback_dsu.hpp"

#include <iostream>

int main() {
    m1une::ds::RollbackDsu dsu(4);
    dsu.merge(0, 1);
    int checkpoint = dsu.snapshot();

    dsu.merge(1, 2);
    std::cout << dsu.same(0, 2) << "\n";  // 1

    dsu.rollback(checkpoint);
    std::cout << dsu.same(0, 2) << "\n";  // 0
    std::cout << dsu.component_count() << "\n";  // 3
}

Required by

Verified with

Code

#ifndef M1UNE_ROLLBACK_DSU_HPP
#define M1UNE_ROLLBACK_DSU_HPP 1

#include <algorithm>
#include <cassert>
#include <vector>

namespace m1une {
namespace ds {

struct RollbackDsu {
   private:
    struct HistoryEntry {
        int first;
        int first_value;
        int second;
        int second_value;
    };

    int _n;
    int _component_count;
    std::vector<int> parent_or_size;
    std::vector<HistoryEntry> history;

    static int check_size(int n) {
        assert(0 <= n);
        return n;
    }

   public:
    RollbackDsu() : RollbackDsu(0) {}

    explicit RollbackDsu(int n)
        : _n(check_size(n)), _component_count(_n), parent_or_size(_n, -1) {}

    int size() const {
        return _n;
    }

    bool empty() const {
        return _n == 0;
    }

    int component_count() const {
        return _component_count;
    }

    int history_size() const {
        return int(history.size());
    }

    void reserve_history(int count) {
        assert(0 <= count);
        history.reserve(count);
    }

    int leader(int vertex) const {
        assert(0 <= vertex && vertex < _n);
        while (parent_or_size[vertex] >= 0) vertex = parent_or_size[vertex];
        return vertex;
    }

    bool same(int first, int second) const {
        return leader(first) == leader(second);
    }

    int group_size(int vertex) const {
        return -parent_or_size[leader(vertex)];
    }

    int size(int vertex) const {
        return group_size(vertex);
    }

    bool merge(int first, int second) {
        first = leader(first);
        second = leader(second);
        if (first == second) {
            history.push_back(HistoryEntry{-1, 0, -1, 0});
            return false;
        }
        if (-parent_or_size[first] < -parent_or_size[second]) {
            std::swap(first, second);
        }
        history.push_back(HistoryEntry{
            first, parent_or_size[first], second, parent_or_size[second]
        });
        parent_or_size[first] += parent_or_size[second];
        parent_or_size[second] = first;
        _component_count--;
        return true;
    }

    bool undo() {
        if (history.empty()) return false;
        const HistoryEntry entry = history.back();
        history.pop_back();
        if (entry.first == -1) return true;
        parent_or_size[entry.first] = entry.first_value;
        parent_or_size[entry.second] = entry.second_value;
        _component_count++;
        return true;
    }

    int snapshot() const {
        return history_size();
    }

    void rollback(int state) {
        assert(0 <= state && state <= history_size());
        while (history_size() > state) undo();
    }

    std::vector<std::vector<int>> groups() const {
        std::vector<int> leader_buffer(_n);
        std::vector<int> group_sizes(_n, 0);
        for (int vertex = 0; vertex < _n; vertex++) {
            leader_buffer[vertex] = leader(vertex);
            group_sizes[leader_buffer[vertex]]++;
        }
        std::vector<std::vector<int>> result(_n);
        for (int vertex = 0; vertex < _n; vertex++) {
            result[vertex].reserve(group_sizes[vertex]);
        }
        for (int vertex = 0; vertex < _n; vertex++) {
            result[leader_buffer[vertex]].push_back(vertex);
        }
        result.erase(
            std::remove_if(
                result.begin(), result.end(),
                [](const std::vector<int>& group) { return group.empty(); }
            ),
            result.end()
        );
        return result;
    }
};

}  // namespace ds
}  // namespace m1une

#endif  // M1UNE_ROLLBACK_DSU_HPP
#line 1 "ds/dsu/rollback_dsu.hpp"



#include <algorithm>
#include <cassert>
#include <vector>

namespace m1une {
namespace ds {

struct RollbackDsu {
   private:
    struct HistoryEntry {
        int first;
        int first_value;
        int second;
        int second_value;
    };

    int _n;
    int _component_count;
    std::vector<int> parent_or_size;
    std::vector<HistoryEntry> history;

    static int check_size(int n) {
        assert(0 <= n);
        return n;
    }

   public:
    RollbackDsu() : RollbackDsu(0) {}

    explicit RollbackDsu(int n)
        : _n(check_size(n)), _component_count(_n), parent_or_size(_n, -1) {}

    int size() const {
        return _n;
    }

    bool empty() const {
        return _n == 0;
    }

    int component_count() const {
        return _component_count;
    }

    int history_size() const {
        return int(history.size());
    }

    void reserve_history(int count) {
        assert(0 <= count);
        history.reserve(count);
    }

    int leader(int vertex) const {
        assert(0 <= vertex && vertex < _n);
        while (parent_or_size[vertex] >= 0) vertex = parent_or_size[vertex];
        return vertex;
    }

    bool same(int first, int second) const {
        return leader(first) == leader(second);
    }

    int group_size(int vertex) const {
        return -parent_or_size[leader(vertex)];
    }

    int size(int vertex) const {
        return group_size(vertex);
    }

    bool merge(int first, int second) {
        first = leader(first);
        second = leader(second);
        if (first == second) {
            history.push_back(HistoryEntry{-1, 0, -1, 0});
            return false;
        }
        if (-parent_or_size[first] < -parent_or_size[second]) {
            std::swap(first, second);
        }
        history.push_back(HistoryEntry{
            first, parent_or_size[first], second, parent_or_size[second]
        });
        parent_or_size[first] += parent_or_size[second];
        parent_or_size[second] = first;
        _component_count--;
        return true;
    }

    bool undo() {
        if (history.empty()) return false;
        const HistoryEntry entry = history.back();
        history.pop_back();
        if (entry.first == -1) return true;
        parent_or_size[entry.first] = entry.first_value;
        parent_or_size[entry.second] = entry.second_value;
        _component_count++;
        return true;
    }

    int snapshot() const {
        return history_size();
    }

    void rollback(int state) {
        assert(0 <= state && state <= history_size());
        while (history_size() > state) undo();
    }

    std::vector<std::vector<int>> groups() const {
        std::vector<int> leader_buffer(_n);
        std::vector<int> group_sizes(_n, 0);
        for (int vertex = 0; vertex < _n; vertex++) {
            leader_buffer[vertex] = leader(vertex);
            group_sizes[leader_buffer[vertex]]++;
        }
        std::vector<std::vector<int>> result(_n);
        for (int vertex = 0; vertex < _n; vertex++) {
            result[vertex].reserve(group_sizes[vertex]);
        }
        for (int vertex = 0; vertex < _n; vertex++) {
            result[leader_buffer[vertex]].push_back(vertex);
        }
        result.erase(
            std::remove_if(
                result.begin(), result.end(),
                [](const std::vector<int>& group) { return group.empty(); }
            ),
            result.end()
        );
        return result;
    }
};

}  // namespace ds
}  // namespace m1une
Back to top page