数列分块技術の入門と問題解説

数列分块入门 1

長さ n の数列を管理し、区間加算と単一点クエリを行う。

解法

ブロックサイズを sqrt(n) に設定し、各ブロックに対して遅延評価を使用する。


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

const int MAXN = 50000 + 1, SQR = 231;
int a[MAXN], bel[MAXN];
int tag[SQR], lp[SQR], rp[SQR];

void updateBlock(int l, int r, int val) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; ++i) a[i] += val;
        return;
    }
    for (int i = l; i <= rp[bel[l]]; ++i) a[i] += val;
    for (int i = lp[bel[r]]; i <= r; ++i) a[i] += val;
    for (int i = bel[l] + 1; i <= bel[r] - 1; ++i) tag[i] += val;
}

int main() {
    int n, k;
    cin >> n;
    k = sqrt(n);
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= k; ++i) {
        lp[i] = (i - 1) * k + 1;
        rp[i] = i * k;
    }
    rp[k] = n;
    for (int i = 1; i <= k; ++i)
        for (int j = lp[i]; j <= rp[i]; ++j) bel[j] = i;
    
    while (n--) {
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if (opt == 0) updateBlock(l, r, c);
        if (opt == 1) cout << a[r] + tag[bel[r]] << endl;
    }
}

数列分块入门 2

長さ n の数列を管理し、区間加算と区間内での k より小さい要素数のカウントを行う。

解法

各ブロックに対してソートされたベクトルを維持し、二分探索でカウントを行う。


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

const int MAXN = 50000 + 1, SQR = 231;
int a[MAXN], bel[MAXN];
int tag[SQR], lp[SQR], rp[SQR];
vector<int> f[SQR];

void rebuildBlock(int id) {
    f[id].clear();
    for (int i = lp[id]; i <= rp[id]; ++i) f[id].push_back(a[i]);
    sort(f[id].begin(), f[id>.end());
}

void updateBlock(int l, int r, int val) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; ++i) a[i] += val;
        rebuildBlock(bel[l]);
        return;
    }
    for (int i = l; i <= rp[bel[l]]; ++i) a[i] += val;
    rebuildBlock(bel[l]);
    for (int i = lp[bel[r]]; i <= r; ++i) a[i] += val;
    rebuildBlock(bel[r]);
    for (int i = bel[l] + 1; i <= bel[r] - 1; ++i) tag[i] += val;
}

int countLessThanK(int l, int r, int k) {
    int ans = 0;
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; ++i) if (a[i] + tag[bel[i]] < k) ++ans;
        return ans;
    }
    for (int i = l; i <= rp[bel[l]]; ++i) if (a[i] + tag[bel[i]] < k) ++ans;
    for (int i = lp[bel[r]]; i <= r; ++i) if (a[i] + tag[bel[i]] < k) ++ans;
    for (int i = bel[l] + 1; i <= bel[r] - 1; ++i) 
        ans += lower_bound(f[i].begin(), f[i].end(), k - tag[i]) - f[i].begin();
    return ans;
}

int main() {
    int n, k;
    cin >> n;
    k = sqrt(n);
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= k; ++i) {
        lp[i] = (i - 1) * k + 1;
        rp[i] = i * k;
    }
    rp[k] = n;
    for (int i = 1; i <= k; ++i) {
        for (int j = lp[i]; j <= rp[i]; ++j) bel[j] = i;
        rebuildBlock(i);
    }

    while (n--) {
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if (opt == 0) updateBlock(l, r, c);
        if (opt == 1) cout << countLessThanK(l, r, c * c) << endl;
    }
}

タグ: 数列分块 区间操作 遅延評価 C++

7月7日 18:04 投稿