莫号模板库

杂项

约定

  1. C++ 版本:
    1. 最低支持版本:c++17
    2. 默认版本:gnu++23
  2. 格式:
    1. 缩进宽度为 4 个空格;
    2. 大括号换行;
    3. 一元运算符不空格,其他运算符空一格;
  3. 命名:
    1. 遵循 GoLang 的命名规范;
  4. 其他:
    1. 使用邻接表存图;
    2. 使用 0-indexed 的左闭右开区间;
    3. 使用解绑同步流的 std::cinstd::cout 输入输出;
    4. 使用 using namespace std;#define int long long
    5. 使用局部变量而非全局变量,局部 lambda 而非全局函数;
    6. 使用 emplace 而非 push,使用 contains 而非 count
    7. 若键值较小,使用 vector 而非 map;不要求顺序的情况下可以用 unordered_ 系列,但在 Codeforces 上记得使用随机模数;

初始代码

#include <bits/stdc++.h>
using namespace std;

#define int long long

int T{0};
void solve() {
}

signed main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    if (!T)
        cin >> T;
    while (T--)
        solve();
}

类型定义

using i8 = signed char;
using u8 = unsigned char;
using i32 = signed;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;

随机数生成

// std::mt19937
mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// splitmix64
struct custom_hash {
    static u64 splitmix64(u64 x) {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }

    u64 operator()(u64 x) const {
        static const u64 SEED =
            std::chrono::high_resolution_clock::now().time_since_epoch().count();
        return splitmix64(x + SEED);
    }
};

二分答案

int l, r, ans;
bool check(int);
while (l <= r) {
    int mid = l + (r - l) / 2;
    if (check(mid)) {
        ans = mid;
        // l 和 r 取决于方向
        l = mid + 1;
    } else {
        r = mid - 1;
    }
}

Lambda 递归

// C++23
auto dfs1 = [&](this auto &&self, int x, int p) -> void {
    for (auto y : adj[x]) {
        if (y == p)
            continue;
        self(y, x);
    }
};
// C++17
std::function<void(int, int)> dfs2 = [&](int x, int p) {
    for (auto y : adj[x]) {
        if (y == p)
            continue;
        dfs2(y, x);
    }
};

数学

线性筛

初始化 [2, N] 内质数和非质数标记。

template <int N>
struct Sieve {
    std::vector<int> pri;
    bool np[N + 1]{};

    Sieve() {
        np[0] = np[1] = true;
        for (int i = 2; i <= N; i++) {
            if (!np[i])
                pri.emplace_back(i);
            for (int p : pri) {
                if (i * p > N)
                    break;
                np[i * p] = true;
                if (i % p == 0)
                    break;
            }
        }
    }
};

因数/约数分解

Factor(n) 返回质因数分解,Divisor(n) 返回大于 1 且小于等于 n 的约数。

inline auto Factor(int n) {
    std::vector<std::pair<int, int>> res;
    for (int i = 2; i * i <= n; i++) {
        int cur = 0;
        while (n % i == 0) {
            cur++;
            n /= i;
        }
        if (cur != 0)
            res.emplace_back(i, cur);
    }
    if (n > 1)
        res.emplace_back(n, 1);
    return res;
}

inline auto Divisor(int n) {
    std::vector<int> res;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            res.emplace_back(i);
            if (i * i != n)
                res.emplace_back(n / i);
        }
    }
    std::sort(res.begin(), res.end());
    return res;
}

扩展欧几里得

返回 gcd(a, b),并求出 ax + by = gcd(a, b) 的一组解。

inline auto ExGCD(auto a, auto b, auto &x, auto &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    auto d = ExGCD(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

快速幂

计算 a^b mod p

inline int PowMod(i64 a, i64 b, int p) {
    i64 res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        b = b >> 1;
        a = a * a % p;
    }
    return res;
}

组合数

基于 ModInt 预处理阶乘和逆阶乘。

template <class Z>
struct Comb {
    std::vector<Z> fac, ifac;

    Comb(int n) : fac(n + 1), ifac(n + 1) {
        fac[0] = 1;
        for (int i = 1; i <= n; i++)
            fac[i] = fac[i - 1] * i;
        ifac[n] = Z(1) / fac[n];
        for (int i = n; i >= 1; i--)
            ifac[i - 1] = ifac[i] * i;
    }

    Z C(int n, int k) const {
        if (k < 0 or k > n)
            return 0;
        return fac[n] * ifac[k] * ifac[n - k];
    }

    Z A(int n, int k) const {
        if (k < 0 or k > n)
            return 0;
        return fac[n] * ifac[n - k];
    }
};

数据结构

并查集 (DSU)

维护无向连通性,Merge(x, y) 返回是否真的合并成功。

struct DSU {
    std::vector<int> f;

    DSU(int n) : f(n) { std::iota(f.begin(), f.end(), 0); }

    int Find(int x) {
        while (x != f[x])
            x = f[x] = f[f[x]];
        return x;
    }

    bool Merge(int x, int y) {
        x = Find(x), y = Find(y);
        if (x == y)
            return false;
        f[y] = x;
        return true;
    }
};

树状数组

维护单点加、前缀和、区间和。

template <typename T> struct Fenwick {
    int n;
    std::vector<T> a;

    Fenwick(int n) : n(n), a(n + 1) {}

    void Add(int x, T v) {
        for (; x <= n; x += x & -x)
            a[x] += v;
    }

    T sum(int x) {
        T res = {};
        for (; x; x -= x & -x)
            res += a[x];
        return res;
    }

    T Sum(int l, int r) { return sum(r) - sum(l - 1); }
};

线段树

维护单点修改、区间查询。

关于 S 的约束:

关于初始区间:

template <class S>
struct SegTree {

    int n;
    std::vector<S> tr;

    SegTree(int n, const S &e) {
        build(n, std::vector<S>(n, e));
    }

    template <std::random_access_iterator It>
    SegTree(It l, It r) {
        build(r - l, l);
    }

    void build(int m, auto &&arr) {
        for (n = 1; n < m; n <<= 1);
        tr.resize(n << 1);
        for (int i = 0; i < m; i++)
            tr[i + n] = arr[i];
        for (int i = n - 1; i >= 1; i--)
            pull(i);
    }

    void pull(int k) {
        tr[k] = tr[k << 1] + tr[k << 1 | 1];
    }

    void Set(int p, const S &x) {
        p += n;
        tr[p] = x;
        for (p >>= 1; p; p >>= 1)
            pull(p);
    }

    S Get(int p) {
        return tr[p + n];
    }

    S Query(int l, int r) {
        l += n, r += n;
        S sml{}, smr{};
        while (l < r) {
            if (l & 1)
                sml = sml + tr[l++];
            if (r & 1)
                smr = tr[--r] + smr;
            l >>= 1;
            r >>= 1;
        }
        return sml + smr;
    }
};

懒标记线段树

使用非递归的实现方式。

关于 F 的约束:

关于 S 的约束:

关于初始区间:

template<class S, class F>
struct LazySegTree {

    int n, h;
    std::vector<S> tr;
    std::vector<F> lz;

    LazySegTree(int n, const S &e) {
        build(n, std::vector<S>(n, e));
    }

    template <std::random_access_iterator It>
    LazySegTree(It l, It r) {
        build(r - l, l);
    }

    void build(int m, auto &&arr) {
        for (n = 1; n < m; n <<= 1);
        h = __builtin_ctz(n);
        tr.resize(n << 1);
        lz.resize(n);
        for (int i = 0; i < m; i++)
            tr[i + n] = arr[i];
        for (int i = n - 1; i >= 1; i--)
            pull(i);
    }

    void apply(int k, const F &f) {
        tr[k] *= f;
        if (k < n)
            lz[k] += f;
    }

    void pull(int k) {
        tr[k] = tr[k << 1] + tr[k << 1 | 1];
    }

    void push(int k) {
        apply(k << 1, lz[k]);
        apply(k << 1 | 1, lz[k]);
        lz[k] = {};
    }

    void Set(int p, const S &x) {
        p += n;
        for (int i = h; i >= 1; i--) push(p >> i);
        tr[p] = x;
        for (int i = 1; i <= h; i++) pull(p >> i);
    }

    S Get(int p) {
        p += n;
        for (int i = h; i >= 1; i--) push(p >> i);
        return tr[p];
    }

    void Update(int l, int r, const F &f) {
        l += n, r += n;
        for (int i = h; i >= 1; i--) {
            if ((l & ((1 << i) - 1)) != 0) push(l >> i);
            if ((r & ((1 << i) - 1)) != 0) push((r - 1) >> i);
        }
        {
            int l_ = l, r_ = r;
            while (l < r) {
                if (l & 1) apply(l++, f);
                if (r & 1) apply(--r, f);
                l >>= 1;
                r >>= 1;
            }
            l = l_;
            r = r_;
        }
        for (int i = 1; i <= h; i++) {
            if ((l & ((1 << i) - 1)) != 0) pull(l >> i);
            if ((r & ((1 << i) - 1)) != 0) pull((r - 1) >> i);
        }
    }

    S Query(int l, int r) {
        l += n, r += n;
        for (int i = h; i >= 1; i--) {
            if ((l & ((1 << i) - 1)) != 0) push(l >> i);
            if ((r & ((1 << i) - 1)) != 0) push((r - 1) >> i);
        }
        S sml{}, smr{};
        while (l < r) {
            if (l & 1)
                sml = sml + tr[l++];
            if (r & 1)
                smr = tr[--r] + smr;
            l >>= 1;
            r >>= 1;
        }
        return sml + smr;
    }
};

异或 Trie

维护非负整数可重集,支持插入、删除和查询与 x 异或的最大值。

struct XorTrie {
    std::vector<std::array<int, 2>> ch;
    std::vector<int> cnt;
    int tot = 1;

    XorTrie(int n) : ch(n * 32), cnt(n * 32) {}

    void Insert(int x) {
        int u = 1;
        cnt[u]++;
        for (int i = 30; i >= 0; i--) {
            int c = (x >> i) & 1;
            if (not ch[u][c]) {
                ch[u][c] = ++tot;
            }
            u = ch[u][c];
            cnt[u]++;
        }
    }

    void Erase(int x) {
        int u = 1;
        cnt[u]--;
        for (int i = 30; i >= 0; i--) {
            int c = (x >> i) & 1;
            u = ch[u][c];
            cnt[u]--;
        }
    }

    int Query(int x) {
        int res = 0;
        int u = 1;
        for (int i = 30; i >= 0; i--) {
            int c = (x >> i) & 1;
            int v = ch[u][c ^ 1];
            if (v and cnt[v] > 0) {
                u = v;
                res |= (1 << i);
            } else {
                u = ch[u][c];
            }
        }
        return res;
    }
};

ModInt

固定模数整数,支持四则运算、输入输出和比较。

using u32 = unsigned;
using i64 = long long; using u64 = unsigned long long;

template <class T> constexpr T power(T a, u64 b, T res = 1) {
    for (; b != 0; b /= 2, a *= a) {
        if (b & 1) {
            res *= a;
        }
    }
    return res;
}

template <u32 P> constexpr u32 mulMod(u32 a, u32 b) { return u64(a) * b % P; }

template <u64 P> constexpr u64 mulMod(u64 a, u64 b) {
    u64 res = a * b - u64(1.L * a * b / P - 0.5L) * P;
    res %= P;
    return res;
}

constexpr i64 safeMod(i64 x, i64 m) {
    x %= m;
    if (x < 0) {
        x += m;
    }
    return x;
}

constexpr std::pair<i64, i64> invGcd(i64 a, i64 b) {
    a = safeMod(a, b);
    if (a == 0) {
        return {b, 0};
    }

    i64 s = b, t = a;
    i64 m0 = 0, m1 = 1;

    while (t) {
        i64 u = s / t;
        s -= t * u;
        m0 -= m1 * u;

        std::swap(s, t);
        std::swap(m0, m1);
    }

    if (m0 < 0) {
        m0 += b / s;
    }

    return {s, m0};
}

template <std::unsigned_integral U, U P> struct ModIntBase {
  public:
    constexpr ModIntBase() : x(0) {}
    template <std::unsigned_integral T> constexpr ModIntBase(T x_) : x(x_ % mod()) {}
    template <std::signed_integral T> constexpr ModIntBase(T x_) {
        using S = std::make_signed_t<U>;
        S v = x_ % S(mod());
        if (v < 0) {
            v += mod();
        }
        x = v;
    }

    constexpr static U mod() { return P; }

    constexpr U val() const { return x; }

    constexpr ModIntBase operator-() const {
        ModIntBase res;
        res.x = (x == 0 ? 0 : mod() - x);
        return res;
    }

    constexpr ModIntBase inv() const { return power(*this, mod() - 2); }

    constexpr ModIntBase &operator*=(const ModIntBase &rhs) & {
        x = mulMod<mod()>(x, rhs.val());
        return *this;
    }
    constexpr ModIntBase &operator+=(const ModIntBase &rhs) & {
        x += rhs.val();
        if (x >= mod()) {
            x -= mod();
        }
        return *this;
    }
    constexpr ModIntBase &operator-=(const ModIntBase &rhs) & {
        x -= rhs.val();
        if (x >= mod()) {
            x += mod();
        }
        return *this;
    }
    constexpr ModIntBase &operator/=(const ModIntBase &rhs) & { return *this *= rhs.inv(); }

    friend constexpr ModIntBase operator*(ModIntBase lhs, const ModIntBase &rhs) {
        lhs *= rhs;
        return lhs;
    }
    friend constexpr ModIntBase operator+(ModIntBase lhs, const ModIntBase &rhs) {
        lhs += rhs;
        return lhs;
    }
    friend constexpr ModIntBase operator-(ModIntBase lhs, const ModIntBase &rhs) {
        lhs -= rhs;
        return lhs;
    }
    friend constexpr ModIntBase operator/(ModIntBase lhs, const ModIntBase &rhs) {
        lhs /= rhs;
        return lhs;
    }

    friend constexpr std::istream &operator>>(std::istream &is, ModIntBase &a) {
        i64 i;
        is >> i;
        a = i;
        return is;
    }
    friend constexpr std::ostream &operator<<(std::ostream &os, const ModIntBase &a) {
        return os << a.val();
    }

    friend constexpr bool operator==(const ModIntBase &lhs, const ModIntBase &rhs) {
        return lhs.val() == rhs.val();
    }
    friend constexpr std::strong_ordering operator<=>(const ModIntBase &lhs,
                                                      const ModIntBase &rhs) {
        return lhs.val() <=> rhs.val();
    }

  private:
    U x;
};

template <u32 P> using ModInt = ModIntBase<u32, P>;
template <u64 P> using ModInt64 = ModIntBase<u64, P>;

图论

单源最短路

适用于非负边权图。

auto dijkstra(const auto &adj, int n, int s) {
    vector<int> dis(n + 1, INF);
    vector<bool> vis(n + 1, false);
    priority_queue<pair<int, int>> pq;
    dis[s] = 0;
    pq.emplace(0, s);
    while (!pq.empty()) {
        auto [_, u] = pq.top(); pq.pop();
        if (vis[u]) 
            continue;
        vis[u] = true;
        for (auto [v, w] : adj[u]) {
            if (dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                pq.emplace(-dis[v], v);
            }
        }
    }
    return dis;
}

分层图最短路

适用于非负边权图上最多使用 k 次特殊操作。

P4568 的建模方式:

复杂度 O((n k + m k) log(n k))

auto LayeredDijkstra(const auto &adj, int n, int s, int k) {
    vector dist(n + 1, vector<i64>(k + 1, INF));
    priority_queue<tuple<i64, int, int>> pq;
    dist[s][0] = 0;
    pq.emplace(0, s, 0);
    while (!pq.empty()) {
        auto [d, u, used] = pq.top(); pq.pop();
        d = -d;
        if (d != dist[u][used])
            continue;
        for (auto [v, w] : adj[u]) {
            if (dist[u][used] + w < dist[v][used]) {
                dist[v][used] = dist[u][used] + w;
                pq.emplace(-dist[v][used], v, used);
            }
            if (used < k and dist[u][used] < dist[v][used + 1]) {
                dist[v][used + 1] = dist[u][used];
                pq.emplace(-dist[v][used + 1], v, used + 1);
            }
        }
    }
    return dist;
}

强连通分量 (SCC)

Tarjan 求有向图强连通分量。

struct SCC {
    int n, now = 0, cnt = 0;
    vector<vector<int>> adj;
    vector<int> dfn, low, stk, ins, id;

    SCC(int n) : n(n), adj(n + 1), dfn(n + 1), low(n + 1), ins(n + 1), id(n + 1) {}

    void AddEdge(int u, int v) {
        adj[u].emplace_back(v);
    }

    void Tarjan(int u) {
        dfn[u] = low[u] = ++now;
        stk.emplace_back(u);
        ins[u] = true;
        for (int v : adj[u]) {
            if (!dfn[v]) {
                Tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (ins[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (dfn[u] == low[u]) {
            cnt++;
            while (true) {
                int x = stk.back();
                stk.pop_back();
                ins[x] = false;
                id[x] = cnt;
                if (x == u)
                    break;
            }
        }
    }

    pair<vector<int>, int> Work() {
        for (int i = 1; i <= n; i++)
            if (!dfn[i])
                Tarjan(i);
        return {id, cnt};
    }
};

割点

Tarjan 求无向图割点。

vector<int> CutVertex(const vector<vector<int>> &adj, int n) {
    vector<int> dfn(n + 1), low(n + 1), cut(n + 1), res;
    int now = 0;
    auto Dfs = [&](this auto &&self, int u, int p) -> void {
        dfn[u] = low[u] = ++now;
        int child = 0;
        for (int v : adj[u]) {
            if (!dfn[v]) {
                child++;
                self(v, u);
                low[u] = min(low[u], low[v]);
                if (p != 0 and low[v] >= dfn[u])
                    cut[u] = true;
            } else if (v != p) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (p == 0 and child >= 2)
            cut[u] = true;
    };
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            Dfs(i, 0);
    for (int i = 1; i <= n; i++)
        if (cut[i])
            res.emplace_back(i);
    return res;
}

二分图最大匹配

匈牙利算法。

int BipartiteMatching(const vector<vector<int>> &adj, int n, int m) {
    vector<int> mt(m + 1), vis(m + 1);
    int ans = 0, stamp = 0;
    auto dfs = [&](this auto &&self, int u) -> bool {
        for (int v : adj[u]) {
            if (vis[v] == stamp)
                continue;
            vis[v] = stamp;
            if (!mt[v] or self(mt[v])) {
                mt[v] = u;
                return true;
            }
        }
        return false;
    };
    for (int i = 1; i <= n; i++) {
        stamp++;
        ans += dfs(i);
    }
    return ans;
}

拓扑排序

Kahn 算法。

vector<int> TopoSort(const vector<vector<int>> &adj, int n) {
    vector<int> indeg(n + 1), res;
    queue<int> q;
    for (int u = 1; u <= n; u++)
        for (int v : adj[u])
            indeg[v]++;
    for (int i = 1; i <= n; i++)
        if (!indeg[i])
            q.emplace(i);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        res.emplace_back(u);
        for (int v : adj[u])
            if (--indeg[v] == 0)
                q.emplace(v);
    }
    return res;
}

差分约束

Bellman-Ford 判负环并求一组可行解。

vector<long long> DifferenceConstraints(int n, const vector<array<int, 3>> &edges) {
    vector<long long> d(n + 1);
    for (int i = 1; i <= n; i++) {
        bool changed = false;
        for (auto [u, v, w] : edges) {
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                changed = true;
            }
        }
        if (!changed)
            break;
        if (i == n)
            return {};
    }
    return d;
}

字符串

KMP

求前缀函数,支持模式串匹配。

vector<int> Kmp(const string &s) {
    int n = s.size();
    vector<int> f(n + 1);
    for (int i = 1, j = 0; i < n; i++) {
        while (j > 0 and s[i] != s[j])
            j = f[j];
        j += (s[i] == s[j]);
        f[i + 1] = j;
    }
    return f;
}

多项式

FFT

复数 FFT 求整数多项式卷积。

using comp = complex<double>;
const double PI = acos(-1);

void Fft(vector<comp> &a, bool inv) {
    int n = a.size();
    for (int i = 1, j = 0; i < n; i++) {
        int bit = n >> 1;
        for (; j & bit; bit >>= 1)
            j ^= bit;
        j ^= bit;
        if (i < j)
            swap(a[i], a[j]);
    }
    for (int len = 2; len <= n; len <<= 1) {
        double ang = 2 * PI / len * (inv ? -1 : 1);
        comp wlen(cos(ang), sin(ang));
        for (int i = 0; i < n; i += len) {
            comp w(1);
            for (int j = 0; j < len / 2; j++) {
                comp u = a[i + j];
                comp v = a[i + j + len / 2] * w;
                a[i + j] = u + v;
                a[i + j + len / 2] = u - v;
                w *= wlen;
            }
        }
    }
    if (inv)
        for (auto &x : a)
            x /= n;
}

vector<long long> Convolution(const vector<long long> &a, const vector<long long> &b) {
    if (a.empty() or b.empty())
        return {};
    int need = a.size() + b.size() - 1;
    int n = 1;
    while (n < need)
        n <<= 1;
    vector<comp> fa(a.begin(), a.end()), fb(b.begin(), b.end());
    fa.resize(n);
    fb.resize(n);
    Fft(fa, false);
    Fft(fb, false);
    for (int i = 0; i < n; i++)
        fa[i] *= fb[i];
    Fft(fa, true);
    vector<long long> res(need);
    for (int i = 0; i < need; i++)
        res[i] = llround(fa[i].real());
    return res;
}