octoの勉強記録

普段の勉強について気が向いたら書きます

部内勉強会(4)

競プロライブラリ集

よく使うライブラリを紹介します

今回はライブラリの使い方だけ紹介して中身の実装の話はほとんどしません(難しいので)

  1. Union Find Tree
  2. Combination
  3. DAG
  4. セグメント木
  5. 遅延セグメント木

Union Find Tree

緑になるには必須のライブラリ

持っていると簡単に解ける問題が多数

集合を管理するデータ構想

できる操作は

  • 同じ集合に属しているか判定 O(1)
  • 集合の併合 O(1)
  • 集合のサイズを取得 O(1)

集合を分離することは出来ないので注意!

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

class UnionFindTree
{
    vector<int> parent;
    vector<int> depth;
    vector<int> cnt;

public:
    UnionFindTree(int n) : parent(n), depth(n), cnt(n)
    {
        for (int i = 0; i < n; i++)
        {
            parent[i] = i;
            depth[i] = 0;
            cnt[i] = 1;
        }
    }

    //木の根を求める
    int find(int x)
    {
        if (parent[x] == x)
        {
            return x;
        }
        else
        {
            return parent[x] = find(parent[x]);
        }
    }

    //xとyの属する集合を併合
    void unite(int x, int y)
    {
        x = find(x);
        y = find(y);
        if (x == y)
            return;
        if (depth[x] < depth[y])
        {
            parent[x] = y;
            cnt[y] += cnt[x];
        }
        else
        {
            parent[y] = x;
            cnt[x] += cnt[y];
            if (depth[x] == depth[y])
                depth[x]++;
        }
    }
    // 同じ集合に属しているか判定
    bool same(int x, int y)
    {
        return find(x) == find(y);
    }
    // 集合のサイズを取得
    int size(int x)
    {
        return cnt[find(x)];
    }
};

int main()
{
    int n, m;
    cin >> n >> m;
    UnionFindTree utree(n + 10);
    for (int i = 0; i < m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        utree.unite(x, y);
    }
    map<int, int> fl;
    int ans = n;
    for (int i = 1; i <= n; i++)
    {
        if (!fl[utree.find(i)])
        {
            fl[utree.find(i)] = 1;
            ans -= utree.size(i) - 1;
        }
    }
    cout << ans << endl;
}

例題

https://atcoder.jp/contests/abc126/tasks/abc126_e

解答

https://atcoder.jp/contests/abc126/submissions/8376002

例題

https://atcoder.jp/contests/abc120/tasks/abc120_d

解答

https://atcoder.jp/contests/abc120/submissions/8376028

例題

https://atcoder.jp/contests/nikkei2019-2-qual/tasks/nikkei2019_2_qual_c

解答

https://atcoder.jp/contests/nikkei2019-2-qual/submissions/8374498

Combination

同じく緑になるには必須のライブラリ

mod上でのnCr, nPr, nHrを高速に計算する

前処理O(N)

計算O(1)

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

class Combination
{
    vector<long long> fact;
    vector<long long> factinv;
    long long MOD = 1e9 + 7;
    long long modpow(long long a, long long n)
    {
        if (n == 0)
            return 1;
        if (n % 2 == 0)
        {
            long long t = modpow(a, n / 2);
            return t * t % MOD;
        }
        return a * modpow(a, n - 1) % MOD;
    }

    long long modinv(long long a)
    {
        // mod is prime
        return modpow(a, MOD - 2);
    }

public:
    Combination(int n) : fact(n), factinv(n)
    {
        fact[0] = 1;
        for (int i = 1; i < n; i++)
        {
            fact[i] = fact[i - 1] * i % MOD;
        }
        factinv[n - 1] = modinv(fact[n - 1]);
        for (int i = n - 2; i >= 0; i--)
        {
            factinv[i] = factinv[i + 1] * (i + 1) % MOD;
        }
    }

    long long ncr(long long n, long long r)
    {
        if (r < 0 || r > n)
            return 0;
        return fact[n] * factinv[r] % MOD * factinv[n - r] % MOD;
    }

    long long npr(long long n, long long r)
    {
        if (r < 0 || r > n)
            return 0;
        return fact[n] * factinv[n - r] % MOD;
    }

    long long nhr(long long n, long long r)
    {
        if (n == 0 && r == 0)
            return 1;
        return ncr(n + r - 1, r);
    }
};

例題

https://atcoder.jp/contests/abc132/tasks/abc132_d

解答

https://atcoder.jp/contests/abc132/submissions/8375997

例題

https://atcoder.jp/contests/abc066/tasks/arc077_b

解答

https://atcoder.jp/contests/abc066/submissions/7944298

DAG

水色になるにはほしいライブラリ

有効グラフを扱うライブラリ

有向グラフ考えるときはとりあえずトポロジカルソートしたくなる

  • トポロジカルソート O(N)
  • ループ判定 O(N)
  • 最長経路 O(N)
#include <bits/stdc++.h>
using namespace std;

class DAG
{
    int n;
    vector<vector<int>> adj;
    vector<int> visited;
    vector<int> dp;
    void dfs(int v)
    {
        if (visited[v] == 2)
        {
            is_acyclic = false;
            return;
        }
        else if (!visited[v])
        {
            visited[v] = 2;
            for (int s : adj[v])
            {
                dfs(s);
            }
            visited[v] = 1;
            sorted.push_back(v);
        }
    }

public:
    vector<int> sorted;
    DAG(int n) : n(n), adj(n), visited(n), dp(n, 0) {}
    bool is_acyclic = true;
    int longest_path = 0;

    //add directed link node a to node b
    void add_arc(int a, int b)
    {
        assert(0 <= a && a < n && 0 <= b && b < n);
        adj[a].push_back(b);
    }

    void tsort()
    {
        for (int i = 0; i < n; i++)
        {
            if (!visited[i])
                dfs(i);
        }
        reverse(sorted.begin(), sorted.end());
    }
    void build()
    {
        for (auto i : sorted)
        {
            for (auto j : adj[i])
            {
                dp[j] = max(dp[j], dp[i] + 1);
                longest_path = max(longest_path, dp[j]);
            }
        }
    }
};

例題

https://atcoder.jp/contests/dp/tasks/dp_g

解答

https://atcoder.jp/contests/dp/submissions/8198624

例題

https://atcoder.jp/contests/abc139/tasks/abc139_e

解答

https://atcoder.jp/contests/abc139/submissions/8079830

セグメント木

青になるまでにあると便利なデータ構造

僕は使わなくても青になれたけど使えたらもっと楽だったなってことが多かった

  • ある1点の更新 O(log N)
  • 区間[L,R)での最小値の取得 O(log N)
  • 前処理 O(N)

最小値だけでなく最大値や和など大抵の計算は扱える非常に汎用性の高いデータ構造

#include <bits/stdc++.h>
using namespace std;
// 引用http://tsutaj.hatenablog.com/entry/2017/03/29/204841
int N, Q;
int const INF = INT_MAX;

struct SegmentTree
{
private:
    int n;
    vector<int> node;

public:
    SegmentTree(vector<int> v)
    {
        int sz = v.size();
        n = 1;
        while (n < sz)
            n *= 2;
        node.resize(2 * n - 1, INF);
        for (int i = 0; i < sz; i++)
            node[i + n - 1] = v[i];
        for (int i = n - 2; i >= 0; i--)
            node[i] = min(node[2 * i + 1], node[2 * i + 2]);
    }

    void update(int x, int val)
    {
        x += (n - 1);
        node[x] = val;
        while (x > 0)
        {
            x = (x - 1) / 2;
            node[x] = min(node[2 * x + 1], node[2 * x + 2]);
        }
    }

    int getmin(int a, int b, int k = 0, int l = 0, int r = -1)
    {
        if (r < 0)
            r = n;
        if (r <= a || b <= l)
            return INF;
        if (a <= l && r <= b)
            return node[k];

        int vl = getmin(a, b, 2 * k + 1, l, (l + r) / 2);
        int vr = getmin(a, b, 2 * k + 2, (l + r) / 2, r);
        return min(vl, vr);
    }
};

int main()
{
    cin >> N >> Q;
    SegmentTree seg(vector<int>(N, INF));
    for (int i = 0; i < Q; i++)
    {
        int c, x, y;
        cin >> c >> x >> y;
        if (c == 0)
            seg.update(x, y);
        else
            cout << seg.getmin(x, y + 1) << endl;
    }
}

例題

https://atcoder.jp/contests/dp/tasks/dp_q

解答

https://atcoder.jp/contests/dp/submissions/8211793

遅延セグメント木

黄色になるまでに使えると便利なデータ構造

セグメント木の上位互換

  • 区間[L, R)の更新 O(log N)
  • 区間[L, R)での最小値の取得 O(log N)
  • 前処理 O(N)

最小値だけでなく最大値や和など大抵の計算は扱える

セグメント木より実装が大変なので脳死で遅延セグメント木を貼るのは危険

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

class LazySegTree
{
    int n;
    vector<long long int> node, lazy;

public:
    LazySegTree(vector<long long int> v)
    {
        int s = int(v.size());
        n = 1;
        while (n < s)
        {
            n <<= 1;
        }
        node.resize(2 * n - 1);
        lazy.resize(2 * n - 1, 0);

        for (int i = 0; i < s; i++)
        {
            node[i + n - 1] = v[i];
        }
        for (int i = n - 2; i >= 0; i--)
        {
            node[i] = node[i * 2 + 1] + node[i * 2 + 2];
        }
    }

    void eval(int k, int l, int r)
    {
        if (lazy[k])
        {
            node[k] += lazy[k];
            if (r - l > 1)
            {
                lazy[2 * k + 1] += lazy[k] / 2;
                lazy[2 * k + 2] += lazy[k] / 2;
            }

            lazy[k] = 0;
        }
    }

    void add(int a, int b, long long int x, int k = 0, int l = 0, int r = -1)
    {
        if (r < 0)
            r = n;
        eval(k, l, r);
        if (b <= l || r <= a)
        {
            return;
        }
        if (a <= l && r <= b)
        {
            lazy[k] += (r - l) * x;
            eval(k, l, r);
        }
        else
        {
            add(a, b, x, 2 * k + 1, l, (l + r) / 2);
            add(a, b, x, 2 * k + 2, (l + r) / 2, r);
            node[k] = node[2 * k + 1] + node[2 * k + 2];
        }
    }

    long long int rangeSum(int a, int b, int k = 0, int l = 0, int r = -1)
    {
        if (r < 0)
            r = n;
        eval(k, l, r);
        if (b <= l || r <= a)
        {
            return 0;
        }
        if (a <= l && r <= b)
        {
            return node[k];
        }
        long long int vl = rangeSum(a, b, 2 * k + 1, l, (l + r) / 2);
        long long int vr = rangeSum(a, b, 2 * k + 2, (l + r) / 2, r);
        return vl + vr;
    }

    void set(int a, long long int x)
    {
        add(a, a + 1, -get(a));
        add(a, a + 1, x);
    }

    long long int get(int a)
    {
        return rangeSum(a, a + 1);
    }
};

int main()
{
    int n;
    cin >> n;
    vector<long long int> as(n);
    LazySegTree seg(as);
    int q;
    cin >> q;
    for (int i = 0; i < q; i++)
    {
        char c;
        cin >> c;
        if (c == 'A')
        {
            int id, x;
            cin >> id >> x;
            seg.set(id - 1, x);
        }
        else
        {
            int l, r;
            cin >> l >> r;
            cout << seg.rangeSum(l - 1, r) << endl;
        }
    }
}

例題

https://atcoder.jp/contests/nikkei2019-2-qual/tasks/nikkei2019_2_qual_d

解答

https://atcoder.jp/contests/nikkei2019-2-qual/submissions/8374973