noya2_Library

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

View the Project on GitHub noya2ruler/noya2_Library

:heavy_check_mark: Mo on Tree
(tree/Mo_on_Tree.hpp)

概要

木上の Mo’s algorithm を扱います。

静的な列のいくつかの区間に対するクエリには Mo’s algorithm が有効でした。これを静的な木のいくつかの単純パスに対するクエリに応用します。

このライブラリは、木上のクエリを列上のクエリに変換し、列上の Mo’s algorithm と同様のインターフェースで扱えるようにしたものです。

しかし、このライブラリが扱える範囲は、列上の Mo’s algorithm が扱えるものよりも狭いです。列上の Mo’s algorithm は区間に含まれる値を集合より強く として解釈したときの計算を行うことができます。一方、このライブラリが扱う木上の Mo’s algorithm は、パスに含まれる値を一般には列としてではなく 集合 として解釈してもできる計算しか行うことができません。

例えば、次の問題 列A, 木A をみてください。

問題 列A

逆元を持つ $d$ 次正方行列の列 $A=(A_1,A_2,\dots ,A_N)$ が与えられます。 クエリが $Q$ 個与えられます。クエリでは列のある区間 $[l,r]$ が与えられるので、行列積 $A_l A_{l+1} \dots A_{r}$ を計算してください。

問題 木A

$N$ 頂点の木が与えられます。各辺には逆元を持つ $d$ 次正方行列が定まっています。 クエリが $Q$ 個与えられます。クエリでは木のある単純パス $u,v$ が与えられるので、 $u$ から $v$ に向かうときに通る辺に定まっている行列を順に掛け合わせていったものを計算してください。

列A は列上の Mo’s algorithm で解くことができます。しかし、木A はこのライブラリの木上の Mo’s algorithm では解くことができません。後述する工夫が必要です。

一方、次の問題 列B, 木B はどうでしょうか。

問題 列B

整数の列 $A=(A_1,A_2,\dots ,A_N)$ が与えられます。 クエリが $Q$ 個与えられます。クエリでは列のある区間 $[l,r]$ が与えられるので、 $A_l A_{l+1} \dots A_{r}$ の種類数を計算してください。

問題 木A

$N$ 頂点の木が与えられます。各辺には整数が定まっています。 クエリが $Q$ 個与えられます。クエリでは木のある単純パス $u,v$ が与えられるので、 $u$ から $v$ に向かうときに通る辺に定まっている整数の種類数を計算してください。

列B はやはり列上の Mo’s algorithm で解くことができます。また、木B はこのライブラリで解くことができます。

Aの問題は演算の順番が決まっていて、順番が異なれば計算結果(行列積)が異なり得るものでした。このため、木A はこのライブラリをそのまま用いることはできず、工夫が必要になります。 一方、Bの問題は演算の順番は決まっておらず、どの順番で計算しても計算結果(種類数)は異ならないのでした。このため木B はこのライブラリをそのまま用いて解くことができます。


木の に重みがある場合と木の 頂点 に重みがある場合で多少実装が異なるので、分けて説明します。

$N$ は扱う木の頂点数とします。木の頂点番号や添字は、特に断らない限り 0_indexed です。

辺に重みがある場合

変換には次の手順を行います。

  1. 木のオイラーツアーを行い、訪れた 頂点 の列 $V=(v_0,v_1,\dots ,v_{2N-2})$ を作る。
  2. 辺の列 $E=(e_0,e_1,\dots ,e_{2N-3})$ を $e_i=(v_i,v_{i+1})$ として定める。
  3. $u,v$ パスに対するクエリは、 $V$ のすべての $u,v$ を含む極小な区間を $[l,r]$ として、 $E$ への区間クエリ $[l,r)$ として解釈する。
  4. 変換した区間に同じ辺が $k$ 個含まれるとき、パスにはその辺は $k\bmod 2$ 個含まれることになる。

ここで、列上の Mo’s algorithm では列の左右が区別できたのに対し、この変換ではパスの向きが失われていることに注意してください。このため、列上の Mo’s algorithm が add_left, add_right, del_left, del_right を要求したのに対し、木上の Mo’s algorithm は左右を区別せず add, del を要求します。そして、内部では区間に含まれる辺の集合を管理しながら、区間の伸縮に応じて add あるいは del を呼び出すようにしています。

adddel が逆元(元に戻す操作)の関係になっている場合は、演算が結合的であるならば、パスに含まれる辺は列として扱うことができます(交換則を満たす必要はありません)。ただし、パスの向きによって答えが変わる場合、このライブラリを素直に扱うことはできません。

しかし、演算の結合順序を逆向きにしたものを用意して $2$ 回アルゴリズムを実行し、どちらで正しい答えが得られるかを適切に判断すれば、解決できます。

演算の結合順序を逆向きにするとは、つまり列への追加 $A\ \overset{\mathrm{add}}\longleftarrow a$ を $A\circ a$ から $a\circ A$ に変更するということです。

MoTree_edge<T>(int n)

コンストラクタです。 $n$ 頂点の木を作る準備をします。辺の重みを保持する型を T とします。

void add_edge(int u, int v, T w)

辺 $(u,v)$ を追加します。辺の重みは $w$ です。

void build(int q)

$q$ クエリあるとし、前処理としてオイラーツアーを行います。

void insert(int u, int v)

$u,v$ パスへのクエリを追加します。

void run(add, del, rem)

辺の重み集合への $w$ の追加および削除を行うラムダ式 add(T w), del(T w) と、 $i$ 番目に追加したクエリの答えを記録するラムダ式 rem(int i) を引数に取り、すべてのクエリを処理します。長さ $2N-2$ の列に対する Mo’s algorithm の計算量になっていることに注意してください。


頂点に重みがある場合

頂点に重みがある場合は少し複雑になります。基本的なアイデアは、辺に重みがある場合に帰着することです。

まず、適当に根をとって木を根付き木にします。そして、根以外の頂点 $v$ に対しては、重みを $v$ の親との間の辺にあるものとします。

そして、パス $(u,v)$ に対するクエリは、 $u,v$ の LCA となる頂点の重みを先ほど帰着した辺に重みがある場合のパスクエリに加えたものとします。

辺に重みがある場合と比べて、重みの集合への追加削除を行う順番がパスに含まれる頂点の順になっていないことに注意してください。

パスに現れる順番で重みを考えたい場合は、辺に重みがある場合と同様の工夫により結合順序を両方用意し、さらにパスを LCA で分割してアルゴリズムを適用し、最後に結果を合わせることができれば良いことになります。結果を合わせることが必ずしも効率的にはできない可能性があります。(例えば、木Bの問題をこの方法で解くには、マージした集合の要素の種類数をマージ前の種類数のみから得る必要がありますが、これは一般には不可能です)

MoTree_vertex<T>(int n, vector<T> b)

コンストラクタです。 $n$ 頂点の木を作る準備をします。頂点の重みを保持する型を T とし、頂点 i の重みが b[i] であるような列を受け取ります。

void add_edge(int u, int v)

辺 $(u,v)$ を追加します。

void build(int q)

$q$ クエリあるとし、前処理としてオイラーツアーを行います。

void insert(int u, int v, int lca)

$u,v$ パスへのクエリを追加します。ただし、木の根を頂点 $0$ としたときの頂点 $u,v$ の LCA を $lca$ とします。

void run(add, del, rem)

頂点の重み集合への $w$ の追加および削除を行うラムダ式 add(T w), del(T w) と、 $i$ 番目に追加したクエリの答えを記録するラムダ式 rem(int i) を引数に取り、すべてのクエリを処理します。長さ $2N-2$ の列に対する Mo’s algorithm の計算量になっていることに注意してください。

Depends on

Verified with

Code

#pragma once

#include"../template/template.hpp"
#include"../misc/mo_algorithm.hpp"

/*

MoTree_edge is verified with : https://atcoder.jp/contests/pakencamp-2022-day1/submissions/43052952

*/

namespace noya2{

template<class T>
struct MoTree_edge {
    int n;
    vector<vector<pair<int,T>>> es;
    MoTree_edge (int _n) : n(_n) {
        es.resize(n);
    }
    void add_edge(int u, int v, T w){
        es[u].emplace_back(v,w);
        es[v].emplace_back(u,w);
    }
    vector<int> in;
    vector<pair<int,T>> vals;
    Mo mo;
    void build(int q){
        int tnow = 0;
        auto dfs = [&](auto dfs, int v, int f) -> void {
            in[v] = tnow++;
            for (auto [u, w] : es[v]){
                if (u == f) continue;
                vals.emplace_back(u,w);
                dfs(dfs,u,v);
                vals.emplace_back(u,w);
                tnow++;
            }
        };
        in.resize(n);
        dfs(dfs,0,-1);
        mo = Mo(2*n-2,q);
    }
    void insert(int u, int v){
        u = in[u], v = in[v];
        if (u > v) swap(u,v);
        mo.insert(u,v);
    }
    template<typename ADD, typename DEL, typename REM>
    void run(const ADD &add, const DEL &del, const REM &rem){
        vector<bool> contain(n,false);
        auto change = [&](int i){
            int id = vals[i].first;
            if (contain[id]){
                del(vals[i].second);
                contain[id] = false;
            }
            else {
                add(vals[i].second);
                contain[id] = true;
            }
        };
        mo.run(change,change,change,change,rem);
    }
};


template<class T>
struct MoTree_vertex {
    int n;
    vector<vector<int>> es;
    vector<T> b;
    MoTree_vertex (int _n, vector<T> _b) : n(_n), b(_b) {
        es.resize(n);
    }
    void add_edge(int u, int v){
        es[u].emplace_back(v);
        es[v].emplace_back(u);
    }
    vector<int> in;
    vector<pair<int,T>> vals;
    vector<int> lcas;
    Mo mo;
    void build(int q){
        vals.reserve(2*n-2);
        lcas.reserve(q);
        int tnow = 0;
        auto dfs = [&](auto dfs, int v, int f) -> void {
            in[v] = tnow++;
            for (auto u : es[v]){
                if (u == f) continue;
                vals.emplace_back(u,b[u]);
                dfs(dfs,u,v);
                vals.emplace_back(u,b[u]);
                tnow++;
            }
        };
        in.resize(n);
        dfs(dfs,0,-1);
        mo = Mo(2*n-2,q);
    }
    
    void insert(int u, int v, int lca){
        u = in[u], v = in[v];
        if (u > v) swap(u,v);
        mo.insert(u,v);
        lcas.push_back(lca);
    }
    template<typename ADD, typename DEL, typename REM>
    void run(const ADD &add, const DEL &del, const REM &rem){
        vector<bool> contain(n,false);
        auto change = [&](int i){
            int id = vals[i].first;
            if (contain[id]){
                del(vals[i].second);
                contain[id] = false;
            }
            else {
                add(vals[i].second);
                contain[id] = true;
            }
        };
        auto rem_add_lca = [&](int i){
            add(b[lcas[i]]);
            rem(i);
            del(b[lcas[i]]);
        };
        mo.run(change,change,change,change,rem_add_lca);
    }
};

} // namespace noya2
#line 2 "tree/Mo_on_Tree.hpp"

#line 2 "template/template.hpp"
using namespace std;

#include<bits/stdc++.h>
#line 1 "template/inout_old.hpp"
namespace noya2 {

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

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

void in() {}
template <typename T, class... U>
void in(T &t, U &...u){
    cin >> t;
    in(u...);
}

void out() { cout << "\n"; }
template <typename T, class... U, char sep = ' '>
void out(const T &t, const U &...u){
    cout << t;
    if (sizeof...(u)) cout << sep;
    out(u...);
}

template<typename T>
void out(const vector<vector<T>> &vv){
    int s = (int)vv.size();
    for (int i = 0; i < s; i++) out(vv[i]);
}

struct IoSetup {
    IoSetup(){
        cin.tie(nullptr);
        ios::sync_with_stdio(false);
        cout << fixed << setprecision(15);
        cerr << fixed << setprecision(7);
    }
} iosetup_noya2;

} // namespace noya2
#line 1 "template/const.hpp"
namespace noya2{

const int iinf = 1'000'000'007;
const long long linf = 2'000'000'000'000'000'000LL;
const long long mod998 =  998244353;
const long long mod107 = 1000000007;
const long double pi = 3.14159265358979323;
const vector<int> dx = {0,1,0,-1,1,1,-1,-1};
const vector<int> dy = {1,0,-1,0,1,-1,-1,1};
const string ALP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string alp = "abcdefghijklmnopqrstuvwxyz";
const string NUM = "0123456789";

void yes(){ cout << "Yes\n"; }
void no(){ cout << "No\n"; }
void YES(){ cout << "YES\n"; }
void NO(){ cout << "NO\n"; }
void yn(bool t){ t ? yes() : no(); }
void YN(bool t){ t ? YES() : NO(); }

} // namespace noya2
#line 2 "template/utils.hpp"

#line 6 "template/utils.hpp"

namespace noya2{

unsigned long long inner_binary_gcd(unsigned long long a, unsigned long long b){
    if (a == 0 || b == 0) return a + b;
    int n = __builtin_ctzll(a); a >>= n;
    int m = __builtin_ctzll(b); b >>= m;
    while (a != b) {
        int mm = __builtin_ctzll(a - b);
        bool f = a > b;
        unsigned long long c = f ? a : b;
        b = f ? b : a;
        a = (c - b) >> mm;
    }
    return a << std::min(n, m);
}

template<typename T> T gcd_fast(T a, T b){ return static_cast<T>(inner_binary_gcd(std::abs(a),std::abs(b))); }

long long sqrt_fast(long long n) {
    if (n <= 0) return 0;
    long long x = sqrt(n);
    while ((x + 1) * (x + 1) <= n) x++;
    while (x * x > n) x--;
    return x;
}

template<typename T> T floor_div(const T n, const T d) {
    assert(d != 0);
    return n / d - static_cast<T>((n ^ d) < 0 && n % d != 0);
}

template<typename T> T ceil_div(const T n, const T d) {
    assert(d != 0);
    return n / d + static_cast<T>((n ^ d) >= 0 && n % d != 0);
}

template<typename T> void uniq(std::vector<T> &v){
    std::sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
}

template <typename T, typename U> inline bool chmin(T &x, U y) { return (y < x) ? (x = y, true) : false; }

template <typename T, typename U> inline bool chmax(T &x, U y) { return (x < y) ? (x = y, true) : false; }

template<typename T> inline bool range(T l, T x, T r){ return l <= x && x < r; }

} // namespace noya2
#line 8 "template/template.hpp"

#define rep(i,n) for (int i = 0; i < (int)(n); i++)
#define repp(i,m,n) for (int i = (m); i < (int)(n); i++)
#define reb(i,n) for (int i = (int)(n-1); i >= 0; i--)
#define all(v) (v).begin(),(v).end()

using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull = unsigned long long;
using pii = pair<int,int>;
using pll = pair<ll,ll>;
using pil = pair<int,ll>;
using pli = pair<ll,int>;

namespace noya2{

/* ~ (. _________ . /) */

}

using namespace noya2;


#line 2 "misc/mo_algorithm.hpp"

/*

usage : https://nyaannyaan.github.io/library/modulo/multipoint-binomial-sum.hpp

*/

#line 10 "misc/mo_algorithm.hpp"

namespace noya2{

struct Mo {
    int width;
    std::vector<int> left, right, order;

    Mo(int N = 1, int Q = 1): order(Q) {
        width = std::max<int>(1, 1.0 * N / std::max<double>(1.0, std::sqrt(Q * 2.0 / 3.0)));
        std::iota(begin(order), end(order), 0);
    }

    void insert(int l, int r) { /* [l, r) */
        left.emplace_back(l);
        right.emplace_back(r);
    }

    template <typename AL, typename AR, typename DL, typename DR, typename REM>
    void run(const AL& add_left, const AR& add_right, const DL& delete_left,
        const DR& delete_right, const REM& rem) {
        assert(left.size() == order.size());
        sort(begin(order), end(order), [&](int a, int b) {
            int ablock = left[a] / width, bblock = left[b] / width;
            if (ablock != bblock) return ablock < bblock;
            if (ablock & 1) return right[a] < right[b];
            return right[a] > right[b];
            });
        int nl = 0, nr = 0;
        for (auto idx : order) {
            while (nl > left[idx]) add_left(--nl);
            while (nr < right[idx]) add_right(nr++);
            while (nl < left[idx]) delete_left(nl++);
            while (nr > right[idx]) delete_right(--nr);
            rem(idx);
        }
    }
};

}
#line 5 "tree/Mo_on_Tree.hpp"

/*

MoTree_edge is verified with : https://atcoder.jp/contests/pakencamp-2022-day1/submissions/43052952

*/

namespace noya2{

template<class T>
struct MoTree_edge {
    int n;
    vector<vector<pair<int,T>>> es;
    MoTree_edge (int _n) : n(_n) {
        es.resize(n);
    }
    void add_edge(int u, int v, T w){
        es[u].emplace_back(v,w);
        es[v].emplace_back(u,w);
    }
    vector<int> in;
    vector<pair<int,T>> vals;
    Mo mo;
    void build(int q){
        int tnow = 0;
        auto dfs = [&](auto dfs, int v, int f) -> void {
            in[v] = tnow++;
            for (auto [u, w] : es[v]){
                if (u == f) continue;
                vals.emplace_back(u,w);
                dfs(dfs,u,v);
                vals.emplace_back(u,w);
                tnow++;
            }
        };
        in.resize(n);
        dfs(dfs,0,-1);
        mo = Mo(2*n-2,q);
    }
    void insert(int u, int v){
        u = in[u], v = in[v];
        if (u > v) swap(u,v);
        mo.insert(u,v);
    }
    template<typename ADD, typename DEL, typename REM>
    void run(const ADD &add, const DEL &del, const REM &rem){
        vector<bool> contain(n,false);
        auto change = [&](int i){
            int id = vals[i].first;
            if (contain[id]){
                del(vals[i].second);
                contain[id] = false;
            }
            else {
                add(vals[i].second);
                contain[id] = true;
            }
        };
        mo.run(change,change,change,change,rem);
    }
};


template<class T>
struct MoTree_vertex {
    int n;
    vector<vector<int>> es;
    vector<T> b;
    MoTree_vertex (int _n, vector<T> _b) : n(_n), b(_b) {
        es.resize(n);
    }
    void add_edge(int u, int v){
        es[u].emplace_back(v);
        es[v].emplace_back(u);
    }
    vector<int> in;
    vector<pair<int,T>> vals;
    vector<int> lcas;
    Mo mo;
    void build(int q){
        vals.reserve(2*n-2);
        lcas.reserve(q);
        int tnow = 0;
        auto dfs = [&](auto dfs, int v, int f) -> void {
            in[v] = tnow++;
            for (auto u : es[v]){
                if (u == f) continue;
                vals.emplace_back(u,b[u]);
                dfs(dfs,u,v);
                vals.emplace_back(u,b[u]);
                tnow++;
            }
        };
        in.resize(n);
        dfs(dfs,0,-1);
        mo = Mo(2*n-2,q);
    }
    
    void insert(int u, int v, int lca){
        u = in[u], v = in[v];
        if (u > v) swap(u,v);
        mo.insert(u,v);
        lcas.push_back(lca);
    }
    template<typename ADD, typename DEL, typename REM>
    void run(const ADD &add, const DEL &del, const REM &rem){
        vector<bool> contain(n,false);
        auto change = [&](int i){
            int id = vals[i].first;
            if (contain[id]){
                del(vals[i].second);
                contain[id] = false;
            }
            else {
                add(vals[i].second);
                contain[id] = true;
            }
        };
        auto rem_add_lca = [&](int i){
            add(b[lcas[i]]);
            rem(i);
            del(b[lcas[i]]);
        };
        mo.run(change,change,change,change,rem_add_lca);
    }
};

} // namespace noya2
Back to top page