View on GitHub

cp-library

$2$ 次元矩形クエリ

C++ 用ライブラリ一覧に戻る

ソースコード

nachia/multi-dimensional/two-d-rectangle-query.hpp

主な機能

$xy$ 平面上の与えられた $n$ 点 $(P _ 0,P _ 1,P _ 2, \ldots , P _ {n-1})$ について、軸に平行な任意の矩形クエリを $1$ 次元区間クエリに変換する。性質上、集約関数の可換性を仮定する。

区間クエリ用のデータ構造の必要数は $\log _ 2 n + O(1)$ で、それぞれが $n$ 点をちょうど $1$ 個ずつ管理する。 $d$ 個目の区間クエリ用のデータ構造の $i$ 番目の位置で管理される点の番号を $A[d][i]$ とする。

struct nachia::TwoDRectangleQuery

テンプレート引数

namespace nachia{

    template<class PosX, class PosY>
    struct TwoDRectangleQuery;

}

PosX , PosY はそれぞれ $X$ 座標、 $Y$ 座標の型。整数型、浮動小数型、その他全順序が成り立つ比較ができる型を設定できる。

コンストラクタ

TwoDRectangleQuery(const std::vector<std::pair<PosX, PosY>>& P);

$P$ を点集合として前処理を行う。 $P$ の各要素は $X$ 座標、 $Y$ 座標の組である。

メモリ使用量には注意すべきだ。

get_segment_count

int get_segment_count() const;

区間クエリを処理するために必要な、区間クエリ用データ構造の個数を返す。

size

int size() const;

$n$ を返す。

to_vtx

int to_vtx(int d, int i) const;

$A[d][i]$ を返す。

get_update_points

struct UpdatePoint{ int d, i; };

std::vector<UpdatePoint> get_update_points(int v);

$A[d][i]=v$ を満たす組 $(d,i)$ をすべて求め、返す。つまり、区間クエリ用データ構造の添え字で、点 $P_v$ に対応するものを列挙する。

to_vtx と逆の関係にある。

get_ranges, get_ranges_from_idx

struct Query{ int d,l,r; };

std::vector<Query> get_ranges(PosX xl, PosX xr, PosY yl, PosY yr); // (1)
std::vector<Query> get_ranges_from_idx(int xl, int xr, int yl, int yr) // (2)

(1) 矩形 $\lbrace (x,y) \vert x _ l\leq x \lt x _ r , y _ l\leq y \lt y _ r \rbrace$ に対する集約クエリを、 $O( \log n )$ 個の $1$ 次元区間の集約クエリで表す。

(2) 与えられた点集合 $P$ を $X$ 座標、 $Y$ 座標で昇順ソートし、それぞれ $P _ X,P _ Y$ とする。このときのソートは安定ではなく、 $P _ X,P _ Y$ は複数回の呼び出しの間に変化しない。 $P _ X$ の $x _ l$ 番目から $x _ {r-1}$ 番目からなる集合と、 $P _ Y$ の $y _ l$ 番目から $y _ {r-1}$ 番目からなる集合の共通部分に対する集約を考える。これを、 $O( \log n )$ 個の $1$ 次元区間の集約クエリで表す。

返す配列の要素 $(d,l,r)$ は、 $A[d][l],A[d][l+1], \ldots ,A[d][r-1]$ に対する集約を表す。すべての区間の集約クエリを処理すると、指定した矩形内の点のみちょうど $1$ 回ずつ集約に含まれる。

使用例

Library Checker : Point Add Rectangle Sum (https://judge.yosupo.jp/problem/point_add_rectangle_sum)

#include <iostream>

#include <nachia/multi-dimensional/two-d-rectangle-query.hpp>

struct BIT{
    std::vector<long long> A;
    BIT(int n=0){ A.assign(n+1,0); }
    long long sum(int r){
        long long res = 0;
        while(r){ res += A[r]; r -= r & -r; }
        return res;
    }
    void add(int p, long long x){
        p += 1;
        while(p < A.size()){ A[p] += x; p += p & -p; }
    }
};


int main(){
    int N,Q; std::cin >> N >> Q;
    std::vector<std::pair<int,int>> P(N);
    std::vector<int> A(N);
    for(int i=0; i<N; i++){
        std::cin >> P[i].first >> P[i].second;
        std::cin >> A[i];
    }

    std::vector<std::vector<int>> queries;

    // read all queries first
    for(int i=0; i<Q; i++){
        int t; std::cin >> t;
        if(t == 0) /* additional point */ {
            int x, y, w; std::cin >> x >> y >> w;
            queries.push_back({ t, (int)P.size(), w });
            P.push_back({ x, y });
            A.push_back(0);
        }
        if(t == 1) /* query */ {
            int xl, yl, xr, yr; std::cin >> xl >> yl >> xr >> yr;
            queries.push_back({ t, xl, xr, yl, yr });
        }
    }

    // 2-dimensional to 1-dimensional
    nachia::TwoDRectangleQuery<int, int> two_dim(P);

    // 1-dimensional data structure
    std::vector<BIT> rq(two_dim.get_segment_count());
    for(int d=0; d<two_dim.get_segment_count(); d++){
        rq[d] = BIT(two_dim.size() + 1);
        for(int i=0; i<two_dim.size(); i++) rq[d].add(i, A[two_dim.to_vtx(d, i)]);
    }

    for(auto& q : queries){
        if(q[0] == 0) /* update */ {
            for(auto qq : two_dim.get_update_points(q[1])){
                rq[qq.d].add(qq.i, q[2]);
            }
        }
        if(q[0] == 1) /* query */ {
            long long ans = 0;
            for(auto qq : two_dim.get_ranges(q[1], q[2], q[3], q[4])){
                ans += rq[qq.d].sum(qq.r) - rq[qq.d].sum(qq.l);
            }
            std::cout << ans << "\n";
        }
    }
    
    return 0;
}

参考

Eating Your Own Cat Food | ウェーブレット行列(wavelet matrix) : https://miti-7.hatenablog.com/entry/2018/04/28/152259


TOP PAGE