LibreOJ 6278. 數列分塊入門 2 題解

題目連接:https://loj.ac/problem/6278c++

題目描述

給出一個長爲 \(n\) 的數列,以及 \(n\) 個操做,操做涉及區間加法,詢問區間內小於某個值 \(x\) 的元素個數。數組

輸入格式

第一行輸入一個數字 \(n\)
第二行輸入 \(n\) 個數字,第 \(i\) 個數字爲 \(a_i\),以空格隔開。
接下來輸入 \(n\) 行詢問,每行輸入四個數字 \(opt\)\(l\)\(r\)\(c\),以空格隔開。
\(opt=0\),表示將位於 \([l,r]\) 的之間的數字都加 \(c\)
\(opt=1\),表示詢問 \([l,r]\) 中,小於 \(c^2\) 的數字的個數。spa

輸出格式

對於每次詢問,輸出一行一個數字表示答案。code

樣例輸入

4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2

樣例輸出

3
0
2

解題思路

一樣仍是按照每一個塊的大小爲 \(\lfloor \sqrt{n} \rfloor\) 來進行分塊。
這裏咱們一樣用 \(p[i]\) 來表示 \(a[i]\) 所屬的分塊編號,用 \(v[k]\) 來表示第 \(k\) 個分塊的累計更新值。
於此同時,咱們再開一個數組 \(b[i]\)\(b\) 數組其實就是 \(a\) 數組的一個映射。那麼它是怎麼映射的呢?
咱們假設 \(a[l..r]\) 屬於同一個分塊,且 \(a[l] 是這個分塊的第一個元素,\)a[r$ 是這個分塊的最後一個元素,那麼 \(b[l..r]\) 就是 \(a[l..r]\) 排好序的結果,即:排序

  • \(b[l]\) 對應 \(a[l..r]\) 中最小的元素;
  • \(b[l+1]\) 對應 \(a[l..r]\) 中次小的元素;
  • ……
  • \(b[r]\) 對應 \(a[l..r]\) 中最大的元素。

一旦咱們修改了某一個分塊 \(k\) 中的部分元素,就須要將分塊 \(k\) 對應的 \(b\) 數組的這段區間排序(對於分塊 \(k\),它對應的座標範圍應該是 \([(k-1) \times m + 1, \min(k \times m, n)]\))。get

修改操做:it

  • 若是區間沒有完整覆蓋分塊 \(k\),則遍歷次分塊中的每個元素,令 \(a[i]+=c\)
  • 不然(完整覆蓋分塊 \(k\)),則令 \(v[k] += c\)

查詢操做:class

  • 若是區間沒有完整覆蓋分塊 \(k\),則遍歷次分塊中的每個元素,判斷 \(a[i] \lt c^2-v[p[i]]\) 是否成立;
  • 不然(完整覆蓋分塊 \(k\)),由於 \(b\) 數組具備單調性,對分塊 \(k\) 包含的區間範圍內的 \(b[l..r]\) 進行二分獲取有多少元素 \(b[i] \lt c^2-v[k]\)

最後將答案彙總。date

每次修改的時間複雜度爲 \(O( \sqrt{n} )\)
每次查詢的時間複雜度爲 \(O( \sqrt{n} \times \sqrt{ \sqrt{n} } ) = O(n^{ \frac 34 })\)遍歷

由於總共有有 \(n\) 次操做,因此整的時間複雜度爲 \(O(n \times n^{ \frac 34 })\)

實現代碼以下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50050;
int n, m, a[maxn], b[maxn], p[maxn], v[300], op, l, r, c;
void update_part(int pid) {
    int i1 = (pid-1)*m+1, i2 = min(pid*m+1, n+1); // 必定要注意邊界條件,我在這裏RE了很久,由於最後一個分塊長度不必定是m
    for (int i = i1; i < i2; i ++)
        b[i] = a[i];
    sort(b+i1, b+i2);
}
void add(int l, int r, int c) {
    if (p[l] == p[r]) { // 說明在同一個分塊,直接更新
        for (int i = l; i <= r; i ++) a[i] += c;
        update_part(p[l]);
        return;
    }
    if (l % m != 1) {    // 說明l不是分塊p[l]的第一個元素
        for (int i = l; p[i]==p[l]; i ++) {
            a[i] += c;
        }
        update_part(p[l]);
    }
    else v[p[l]] += c;
    if (r % m != 0) { // 說明r不是分塊p[r]的最後一個元素
        for (int i = r; p[i]==p[r]; i --)
            a[i] += c;
        update_part(p[r]);
    }
    else v[p[r]] += c;
    for (int i = p[l]+1; i < p[r]; i ++)
        v[i] += c;
}
int count_part(int pid, int c) {
    int i1 = (pid-1)*m+1, i2 = min(pid*m+1, n+1);
    int cnt = lower_bound(b+i1, b+i2, c*c-v[pid]) - (b+i1);
    return cnt;
}
int get_count(int l, int r, int c) {
    int cnt = 0;
    if (p[l] == p[r]) { // 說明在同一個分塊,直接更新
        for (int i = l; i <= r; i ++)
            if (a[i]+v[p[i]] < c*c)
                cnt ++;
        return cnt;
    }
    if (l % m != 1) {    // 說明l不是分塊p[l]的第一個元素
        for (int i = l; p[i]==p[l]; i ++)
            if (a[i]+v[p[i]] < c*c)
                cnt ++;
    }
    else cnt += count_part(p[l], c);
    if (r % m != 0) { // 說明r不是分塊p[r]的最後一個元素
        for (int i = r; p[i]==p[r]; i --)
            if (a[i]+v[p[i]] < c*c)
                cnt ++;
    }
    else cnt += count_part(p[r], c);
    for (int i = p[l]+1; i < p[r]; i ++)
        cnt += count_part(i, c);
    return cnt;
}
int main() {
    scanf("%d", &n);
    m = sqrt(n);
    for (int i = 1; i <= n; i ++) p[i] = (i-1)/m + 1;
    for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    for (int i = m; i <= n; i += m) update_part(p[i]);    // 初始化全部完整的塊
    for (int i = 0; i < n; i ++) {
        scanf("%d%d%d%d", &op, &l, &r, &c);
        if (op == 0) add(l, r, c);
        else printf("%d\n", get_count(l, r, c));
    }
    return 0;
}
相關文章
相關標籤/搜索