しょぼんブログ

数学の色々とか様々とか

連載 しょぼんコーダー 番外編1 平方分割を実装してみた

バグループ

聴いてください、バグループ
バグってない平方分割を知らない
バグってない平方分割が実装できない
バグってない平方分割を知らない
バグってない平方分割が実装できないよ

蟻本 pp169-170 を参考にして, 自分なりに平方分割を実装してみた.

Library Checker - Static RMQ Library Checker

区間最小値はセグメント木でも行けるが, 平方分割でも行ける.

コード

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

int main(){
    int n,q;
    cin >> n >> q;
    int b = int(pow(n, 0.5) + 1);
    vector<int> a(n);
    for (int i=0; i<n; i++){
        cin >> a[i];
    }

    vector<int> dat(b, 1e9);
    for (int i=0; i<b; i++){
        for (int j=min(n, i*b); j<min(n, (i+1)*b); j++){
            dat[i] = min(dat[i], a[j]);
        }
    }

    for (int num=0; num<q; num++){
        int l, r;
        cin >> l >> r;
        int tl = l+b-(l%b), tr = r-(r%b), ans = 1e9;
        for (int i=l; i<min(r,tl); i++){
            ans = min(ans, a[i]);
        }
        for (int i=tl/b; i<tr/b; i++){
            ans = min(ans, dat[i]);
        }
        for (int i=max(tl,tr); i<r; i++){
            ans = min(ans, a[i]);
        }
        cout << ans << endl;
    }
}

説明

細かくみていく.

b

int b = int(pow(n, 0.5) + 1);

bはバケットのサイズでもあるが, バケットの数でもある. +1でうまい具合に調整してある.

前処理

vector<int> dat(b, 1e9);
for (int i=0; i<b; i++){
    for (int j=min(n, i*b); j<min(n, (i+1)*b); j++){
        dat[i] = min(dat[i], a[j]);
    }
}

前処理はこんな感じ. 各バケットについて計算している. ここは O(N)

(追記 2022/09/03)for文だと必要ないが, sortだとバケットの左端jを i*b としてしまうと配列外にアクセスしてしまう. これを防ぐために, ここの時点で for int j min(n, i*b) と書いている.

RMQ

for (int num=0; num<q; num++){
    int l, r;
    cin >> l >> r;
    int tl = l+b-(l%b), tr = r-(r%b), ans = 1e9;

    // (1)
    for (int i=l; i<min(r,tl); i++){
        ans = min(ans, a[i]);
    }

    // (2)
    for (int i=tl/b; i<tr/b; i++){
        ans = min(ans, dat[i]);
    }

    // (3)
    for (int i=max(tl,tr); i<r; i++){
        ans = min(ans, a[i]);
    }
    
    cout << ans << endl;
}

ここはクエリごとの処理. ここは O(Q\sqrt{N}). 蟻本と違うのは, 左から順番に見ていることと, while文を使っていないこと.

tl は, l から右に行って最も近いブロックを示し, tr は, r から左に行って最も近いブロックを示す.

蟻本でも tl, tr を使っているが, ちょっとだけ違う. (1) と (3) でそれぞれforの区間にmin, maxを使っている. これは下図のような状態を防ぐためである. 余計なものまで計算してしまい正しい結果が出ない.

左から順番に見ているのは, 左からやっていく場合に使いたいからである. 蟻本のような実装だと, tlは左から右へ移動するが, trは右から左に移動する. これでは操作を左からやっていきたいときに困る.

while文をfor文にしたのは, while文だとちょっと遅いからである(定数倍高速化). while文で実装したときは1400msだったが, for文で実装したら858msだった.

while文
https://judge.yosupo.jp/submission/101976

for文
https://judge.yosupo.jp/submission/101979