library_for_cpp

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

View the Project on GitHub Kazun1998/library_for_cpp

:heavy_check_mark: ポテンシャル付き Union Find
(Union_Find/Potentilized_Union_Find.hpp)

Outline

Union Find にポテンシャルの概念を付け加え, $2$ 頂点間のポテンシャルの差を取得する.

Define

$G$ を群とする.

各弧に $G$ の重みが付いた有向グラフ $D = (V, A, W: V \to G)$ において, 以下をみたす時, この $D$ は対称的であるという.


各弧に $G$ の重みが付いた有向グラフ $D = (V, A, W: V \to G)$ 上のパス $p = (v_0, v_1, v_2, \dots, v_m)$ に対して, $p$ のポテンシャルエネルギー $E(p)$ を

\[E(p) := \sum_{j=1}^m W\left(\overrightarrow{v_{j-1} v_j} \right)\]

で定義する.


対称的な各弧に $G$ の重みが付いた有向グラフ $D = (V, A, W: V \to G)$ について, 以下の条件をみたす時, $D$ はポテンシャルを持つという.


$D$ はポテンシャルを持つとする.

\[R := \{(u, v) \in V^2 \mid v \text{ is reachable from } u \text{ on } D. \}\]

とする. このとき, $D$ におけるポテンシャル $P: R \to G$ を $u$ を始点, $v$ を終点とするパスを $p$ としたとき, $P(u, v) := E(p)$ とする. $D$ がポテンシャルを持つことから, Well-Defined 性が従う.

Theory

対称的な各弧に $G$ の重みが付いた有向グラフ $D = (V, A, W: V \to G)$ について, 以下は同値になる.

Remark

このライブラリで対応しているのは $P(x) = a P(y)$ の形である, 左作用である. もし, 右作用「のみ」を要求されている場合は, 次のようにして対応させることができる.

Contents

Constructor

template<typename Pot>
Potentilized_Union_Find(int n, function<Pot(const Pot&, const Pot&)> add, const Pot &zero, const function<Pot(const Pot &)> neg)

find

int find(int x)

same

bool same(int x, int y)

size

int size(int x)

unite

bool unite(int x, int y, Pot a)

potential

Pot potential(int x)
Pot potential(int x, int y)

is_valid

bool is_valid(int x)

is_well_defined

bool is_well_defined(int x, int y)

is_possible

bool is_possible(int x, int y, Pot a)

group_count

int group_count()

History

日付 内容
2025/11/24 ポテンシャルの群が非可換群である場合にも対応
2025/11/23 ポテンシャル付き Union Find を実装

Depends on

Verified with

Code

#pragma once

#include"../template/template.hpp"

template<typename Pot>
class Potentilized_Union_Find {
    int n, _group_number;
    vector<int> par, rank;
    vector<Pot> pot; // P(x) = pot[x] P(par[x])
    vector<bool> valid;

    Pot unit;
    function<Pot(const Pot&, const Pot&)> add;
    function<Pot(const Pot&, const Pot&)> diff;
    function<Pot(const Pot&)> neg;

    public:
    Potentilized_Union_Find(int n, function<Pot(const Pot&, const Pot&)> add, const Pot &unit, const function<Pot(const Pot &)> neg):
        n(n), par(vector<int>(n, -1)), rank(vector<int>(n, 0)), pot(vector<Pot>(n, unit)), valid(vector<bool>(n, true)), _group_number(n),
        unit(unit), add(add), neg(neg) {
            diff = [&add, &neg](const Pot& a, const Pot& b) { return add(neg(a), b); };
        }

    /// @brief 頂点 x が属する連結成分の代表元を求める.
    /// @param x 
    int find(int x) {
        // x 自身が代表元ならば, x で決定
        if (par[x] < 0) { return x; }

        int r = find(par[x]);
        pot[x] = add(pot[x], pot[par[x]]);
        return par[x] = r;
    }

    /// @brief 頂点 x, y は連結か?
    /// @param x 
    /// @param y 
    inline bool same(int x, int y) { return find(x) == find(y); }

    /// @brief 頂点 x が属する連結成分における頂点数を求める.
    /// @param x 
    inline int size(int x) { return -par[find(x)]; }

    /// @brief P(x) = a P(y) という情報を加える.
    /// @param x 
    /// @param y 
    /// @param a 
    /// @return x, y の間が無矛盾ならば true, 矛盾があれば false.
    bool unite(int x, int y, Pot a) {
        a = add(a, potential(y));
        a = add(neg(potential(x)), a);

        x = find(x), y = find(y);

        if (x == y) {
            valid[x] = valid[x] && (a == unit);
            return valid[x];
        }

        if (rank[x] > rank[y]) {
            swap(x, y);
            a = neg(a);
        }

        if (rank[x] == rank[y]) { rank[x]++; }

        valid[y] = valid[x] && valid[y];

        par[y] += par[x];

        par[x] = y;
        pot[x] = a;

        _group_number--;

        return true;
    }

    Pot potential(int x) {
        find(x);
        return pot[x];
    }

    /// @brief x から見た y のポテンシャルを求める. つまり, -P(y) + P(x) を求める.
    /// @param x 基準
    /// @param y ポテンシャルを求める点
    Pot potential(int x, int y) { return add(potential(x), neg(potential(y))); }

    bool is_valid(int x) { return valid[x]; }

    /// @brief x - y 間のポテンシャルは位置に定まるか?
    /// @param x 
    /// @param y 
    /// @return 
    inline bool is_well_defined(int x, int y) { return same(x, y) && is_valid(x); }

    /// @brief P(x) = P(y) + a となり得るか?
    /// @param x 
    /// @param y 
    /// @param a 
    /// @return 
    bool is_possible(int x, int y, Pot a) { return !same(x, y) || potential(x, y) == a; }

    /// @brief 連結成分の数
    int group_count() const { return _group_number; }
};
#line 2 "Union_Find/Potentilized_Union_Find.hpp"

#line 2 "template/template.hpp"

using namespace std;

// intrinstic
#include <immintrin.h>

#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cctype>
#include <cfenv>
#include <cfloat>
#include <chrono>
#include <cinttypes>
#include <climits>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <fstream>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <ios>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <optional>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <stack>
#include <streambuf>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

// utility
#line 2 "template/utility.hpp"

using ll = long long;

// a ← max(a, b) を実行する. a が更新されたら, 返り値が true.
template<typename T, typename U>
inline bool chmax(T &a, const U b){
    return (a < b ? a = b, 1: 0);
}

// a ← min(a, b) を実行する. a が更新されたら, 返り値が true.
template<typename T, typename U>
inline bool chmin(T &a, const U b){
    return (a > b ? a = b, 1: 0);
}

// a の最大値を取得する.
template<typename T>
inline T max(const vector<T> &a){
    if (a.empty()) throw invalid_argument("vector is empty.");

    return *max_element(a.begin(), a.end());
}

// vector<T> a の最小値を取得する.
template<typename T>
inline T min(const vector<T> &a){
    if (a.empty()) throw invalid_argument("vector is empty.");

    return *min_element(a.begin(), a.end());
}

// vector<T> a の最大値のインデックスを取得する.
template<typename T>
inline size_t argmax(const vector<T> &a){
    if (a.empty()) throw std::invalid_argument("vector is empty.");

    return distance(a.begin(), max_element(a.begin(), a.end()));
}

// vector<T> a の最小値のインデックスを取得する.
template<typename T>
inline size_t argmin(const vector<T> &a){
    if (a.empty()) throw invalid_argument("vector is empty.");

    return distance(a.begin(), min_element(a.begin(), a.end()));
}
#line 61 "template/template.hpp"

// math
#line 2 "template/math.hpp"

// 演算子
template<typename T>
T add(const T &x, const T &y) { return x + y; }

template<typename T>
T sub(const T &x, const T &y) { return x - y; }

template<typename T>
T mul(const T &x, const T &y) { return x * y; }

template<typename T>
T neg(const T &x) { return -x; }

template<integral T>
T bitwise_and(const T &x, const T &y) { return x & y; }

template<integral T>
T bitwise_or(const T &x, const T &y) { return x | y; }

template<integral T>
T bitwise_xor(const T &x, const T &y) { return x ^ y; }

// 除算に関する関数

// floor(x / y) を求める.
template<integral T, integral U>
auto div_floor(T x, U y){
    return x / y - ((x % y != 0) && ((x < 0) != (y < 0)));
}

// ceil(x / y) を求める.
template<integral T, integral U>
auto div_ceil(T x, U y){
    return x / y + ((x % y != 0) && ((x < 0) == (y < 0)));
}

// x を y で割った余りを求める.
template<integral T, integral U>
auto safe_mod(T x, U y){
    auto q = div_floor(x, y);
    return x - q * y ;
}

// x を y で割った商と余りを求める.
template<integral T, integral U>
auto divmod(T x, U y){
    auto q = div_floor(x, y);
    return make_pair(q, x - q * y);
}

// 四捨五入を求める.
template<integral T, integral U>
auto round(T x, U y){
    auto [q, r] = divmod(x, y);
    if (y < 0) return (r <= div_floor(y, 2)) ? q + 1 : q;
    return (r >= div_ceil(y, 2)) ? q + 1 : q;
}

// 奇数かどうか判定する.
template<integral T>
bool is_odd(const T &x) { return x % 2 != 0; }

// 偶数かどうか判定する.
template<integral T>
bool is_even(const T &x) { return x % 2 == 0; }

// m の倍数かどうか判定する.
template<integral T, integral U>
bool is_multiple(const T &x, const U &m) { return x % m == 0; }

// 正かどうか判定する.
template<typename T>
bool is_positive(const T &x) { return x > 0; }

// 負かどうか判定する.
template<typename T>
bool is_negative(const T &x) { return x < 0; }

// ゼロかどうか判定する.
template<typename T>
bool is_zero(const T &x) { return x == 0; }

// 非負かどうか判定する.
template<typename T>
bool is_non_negative(const T &x) { return x >= 0; }

// 非正かどうか判定する.
template<typename T>
bool is_non_positive(const T &x) { return x <= 0; }

// 指数に関する関数

// x の y 乗を求める.
ll intpow(ll x, ll y){
    ll a = 1;
    while (y){
        if (y & 1) { a *= x; }
        x *= x;
        y >>= 1;
    }
    return a;
}

// x の y 乗を z で割った余りを求める.
template<typename T, integral U>
T modpow(T x, U y, T z) {
    T a = 1;
    while (y) {
        if (y & 1) { (a *= x) %= z; }

        (x *= x) %= z;
        y >>= 1;
    }

    return a;
}

template<typename T>
T sum(const vector<T> &X) {
    T y = T(0);
    for (auto &&x: X) { y += x; }
    return y;
}

template<typename T>
T gcd(const T x, const T y) {
    return y == 0 ? x : gcd(y, x % y);
}

// a x + b y = gcd(a, b) を満たす整数の組 (a, b) に対して, (x, y, gcd(a, b)) を求める.
template<integral T>
tuple<T, T, T> Extended_Euclid(T a, T b) {
    T s = 1, t = 0, u = 0, v = 1;
    while (b) {
        auto [q, r] = divmod(a, b);
        a = b;
        b = r;
        tie(s, t) = make_pair(t, s - q * t);
        tie(u, v) = make_pair(v, u - q * v);
    }

    return make_tuple(s, u, a);
}

// floor(sqrt(N)) を求める (N < 0 のときは, 0 とする).
ll isqrt(const ll &N) { 
    if (N <= 0) { return 0; }

    ll x = sqrtl(N);
    while ((x + 1) * (x + 1) <= N) { x++; }
    while (x * x > N) { x--; }

    return x;
}

// floor(sqrt(N)) を求める (N < 0 のときは, 0 とする).
ll floor_sqrt(const ll &N) { return isqrt(N); }

// ceil(sqrt(N)) を求める (N < 0 のときは, 0 とする).
ll ceil_sqrt(const ll &N) {
    ll x = isqrt(N);
    return x * x == N ? x : x + 1;
}
#line 64 "template/template.hpp"

// inout
#line 1 "template/inout.hpp"
// 入出力
template<class... T>
void input(T&... a){ (cin >> ... >> a); }

void print(){ cout << "\n"; }

template<class T, class... Ts>
void print(const T& a, const Ts&... b){
    cout << a;
    (cout << ... << (cout << " ", b));
    cout << "\n";
}

template<typename T, typename U>
istream &operator>>(istream &is, pair<T, U> &P){
    is >> P.first >> P.second;
    return is;
}

template<typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &P){
    os << P.first << " " << P.second;
    return os;
}

template<typename T>
vector<T> vector_input(int N, int index){
    vector<T> X(N+index);
    for (int i=index; i<index+N; i++) cin >> X[i];
    return X;
}

template<typename T>
istream &operator>>(istream &is, vector<T> &X){
    for (auto &x: X) { is >> x; }
    return is;
}

template<typename T>
ostream &operator<<(ostream &os, const vector<T> &X){
    int s = (int)X.size();
    for (int i = 0; i < s; i++) { os << (i ? " " : "") << X[i]; }
    return os;
}

template<typename T>
ostream &operator<<(ostream &os, const unordered_set<T> &S){
    int i = 0;
    for (T a: S) {os << (i ? " ": "") << a; i++;}
    return os;
}

template<typename T>
ostream &operator<<(ostream &os, const set<T> &S){
    int i = 0;
    for (T a: S) { os << (i ? " ": "") << a; i++; }
    return os;
}

template<typename T>
ostream &operator<<(ostream &os, const unordered_multiset<T> &S){
    int i = 0;
    for (T a: S) { os << (i ? " ": "") << a; i++; }
    return os;
}

template<typename T>
ostream &operator<<(ostream &os, const multiset<T> &S){
    int i = 0;
    for (T a: S) { os << (i ? " ": "") << a; i++; }
    return os;
}

template<typename T>
std::vector<T> input_vector(size_t n, size_t offset = 0) {
    std::vector<T> res;
    // 最初に必要な全容量を確保(再確保を防ぐ)
    res.reserve(n + offset);
    // offset 分をデフォルト値で埋める(特別 indexed 用)
    res.assign(offset, T());
    
    for (size_t i = 0; i < n; ++i) {
        T el;
        if (!(std::cin >> el)) break;
        res.push_back(std::move(el));
    }
    return res;
}
#line 67 "template/template.hpp"

// macro
#line 2 "template/macro.hpp"

// マクロの定義
#define all(x) x.begin(), x.end()
#define len(x) ll(x.size())
#define elif else if
#define unless(cond) if (!(cond))
#define until(cond) while (!(cond))
#define loop while (true)

// オーバーロードマクロ
#define overload2(_1, _2, name, ...) name
#define overload3(_1, _2, _3, name, ...) name
#define overload4(_1, _2, _3, _4, name, ...) name
#define overload5(_1, _2, _3, _4, _5, name, ...) name

// 繰り返し系
#define rep1(n) for (ll i = 0; i < n; i++)
#define rep2(i, n) for (ll i = 0; i < n; i++)
#define rep3(i, a, b) for (ll i = a; i < b; i++)
#define rep4(i, a, b, c) for (ll i = a; i < b; i += c)
#define rep(...) overload4(__VA_ARGS__, rep4, rep3, rep2, rep1)(__VA_ARGS__)

#define foreach1(x, a) for (auto &&x: a)
#define foreach2(x, y, a) for (auto &&[x, y]: a)
#define foreach3(x, y, z, a) for (auto &&[x, y, z]: a)
#define foreach4(x, y, z, w, a) for (auto &&[x, y, z, w]: a)
#define foreach(...) overload5(__VA_ARGS__, foreach4, foreach3, foreach2, foreach1)(__VA_ARGS__)
#line 70 "template/template.hpp"

// bitop
#line 2 "template/bitop.hpp"

// 非負整数 x の bit legnth を求める.
ll bit_length(ll x) {
    if (x == 0) { return 0; }
    return (sizeof(long) * CHAR_BIT) - __builtin_clzll(x);
}

// 非負整数 x の popcount を求める.
ll popcount(ll x) { return __builtin_popcountll(x); }

// 正の整数 x に対して, floor(log2(x)) を求める.
ll floor_log2(ll x) { return bit_length(x) - 1; }

// 正の整数 x に対して, ceil(log2(x)) を求める.
ll ceil_log2(ll x) { return bit_length(x - 1); }

// x の第 k ビットを取得する
int get_bit(ll x, int k) { return (x >> k) & 1; }

// x のビット列を取得する.
// k はビット列の長さとする.
vector<int> get_bits(ll x, int k) {
    vector<int> bits(k);
    rep(i, k) {
        bits[i] = x & 1;
        x >>= 1;
    }

    return bits;
}

// x のビット列を取得する.
vector<int> get_bits(ll x) { return get_bits(x, bit_length(x)); }
#line 73 "template/template.hpp"

// exception
#line 2 "template/exception.hpp"

class NotExist: public exception {
    private:
    string message;

    public:
    NotExist() : message("求めようとしていたものは存在しません.") {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};
#line 4 "Union_Find/Potentilized_Union_Find.hpp"

template<typename Pot>
class Potentilized_Union_Find {
    int n, _group_number;
    vector<int> par, rank;
    vector<Pot> pot; // P(x) = pot[x] P(par[x])
    vector<bool> valid;

    Pot unit;
    function<Pot(const Pot&, const Pot&)> add;
    function<Pot(const Pot&, const Pot&)> diff;
    function<Pot(const Pot&)> neg;

    public:
    Potentilized_Union_Find(int n, function<Pot(const Pot&, const Pot&)> add, const Pot &unit, const function<Pot(const Pot &)> neg):
        n(n), par(vector<int>(n, -1)), rank(vector<int>(n, 0)), pot(vector<Pot>(n, unit)), valid(vector<bool>(n, true)), _group_number(n),
        unit(unit), add(add), neg(neg) {
            diff = [&add, &neg](const Pot& a, const Pot& b) { return add(neg(a), b); };
        }

    /// @brief 頂点 x が属する連結成分の代表元を求める.
    /// @param x 
    int find(int x) {
        // x 自身が代表元ならば, x で決定
        if (par[x] < 0) { return x; }

        int r = find(par[x]);
        pot[x] = add(pot[x], pot[par[x]]);
        return par[x] = r;
    }

    /// @brief 頂点 x, y は連結か?
    /// @param x 
    /// @param y 
    inline bool same(int x, int y) { return find(x) == find(y); }

    /// @brief 頂点 x が属する連結成分における頂点数を求める.
    /// @param x 
    inline int size(int x) { return -par[find(x)]; }

    /// @brief P(x) = a P(y) という情報を加える.
    /// @param x 
    /// @param y 
    /// @param a 
    /// @return x, y の間が無矛盾ならば true, 矛盾があれば false.
    bool unite(int x, int y, Pot a) {
        a = add(a, potential(y));
        a = add(neg(potential(x)), a);

        x = find(x), y = find(y);

        if (x == y) {
            valid[x] = valid[x] && (a == unit);
            return valid[x];
        }

        if (rank[x] > rank[y]) {
            swap(x, y);
            a = neg(a);
        }

        if (rank[x] == rank[y]) { rank[x]++; }

        valid[y] = valid[x] && valid[y];

        par[y] += par[x];

        par[x] = y;
        pot[x] = a;

        _group_number--;

        return true;
    }

    Pot potential(int x) {
        find(x);
        return pot[x];
    }

    /// @brief x から見た y のポテンシャルを求める. つまり, -P(y) + P(x) を求める.
    /// @param x 基準
    /// @param y ポテンシャルを求める点
    Pot potential(int x, int y) { return add(potential(x), neg(potential(y))); }

    bool is_valid(int x) { return valid[x]; }

    /// @brief x - y 間のポテンシャルは位置に定まるか?
    /// @param x 
    /// @param y 
    /// @return 
    inline bool is_well_defined(int x, int y) { return same(x, y) && is_valid(x); }

    /// @brief P(x) = P(y) + a となり得るか?
    /// @param x 
    /// @param y 
    /// @param a 
    /// @return 
    bool is_possible(int x, int y, Pot a) { return !same(x, y) || potential(x, y) == a; }

    /// @brief 連結成分の数
    int group_count() const { return _group_number; }
};
Back to top page