数列分块入门1-9 LibreOJ

我也不知道为什么一个大二的ACM选手没学分块。

我怎么记得大一的时候,学长教给我的分块就只有 block 和 num 两个变量来着...好吧,应该是我没认真学。正好前两天朋友给学弟开课,乘机去蹭了一节课。然后...我还是不会哇,菜的一塌糊涂。

还是 卿学姐 好哇,多听几遍,睡得贼香

分块原理

分块嘛,其实就是优雅的暴力,和莫队(不会)有点异曲同工的赶脚。通过将数组分成小块以降低复杂度。

通常情况下:

  • 每个块的大小(block)为 \(\sqrt{n}\)
  • 块数(num)为 \(\sqrt{n}\)\(\sqrt{n}+1\)
  • 每个块的左端点(L[x])为 \((i-1)*block+1\)
  • 每个块的右端点(R[x])为 \(i*block\)(最后一块右端点为\(n\)
  • 位置 x 上的数属于(belong[x])第 \((i-1)/block+1\)

每次修改或查询的时候如果需要维护 [L, R] 的数,如果L、R被分在了同一块,那么就直接暴力跑就行了,反正复杂度不会超过\(\sqrt{n}\)

如果不在一块,那就先暴力更新所有和 L 同一块且位置在 L 之后的数,再更新所有和 R 在同一块且位置在 R 之前的数,最后,再去把这中间的若干块直接按“块”更新(利用新数组之类的)。这样的复杂度也只有 \(\sqrt{n}\)量级。从而完成多次优雅的在线更新和查询。

题解

LOJ 6277 区间加法更新 单点查询

没啥好些的,就按照上边写的来就行。因为是纯板子题,所以还是放个代码好了。

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

const int maxn = 5e4 + 5;

int belong[maxn];
int block, num;
int l[maxn], r[maxn];
int n;
int a[maxn];
int more[maxn];

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block != 0) {
        num++;
    }
    for (int i = 1; i <= num; i++) {
        l[i] = (i - 1) * block + 1;
        r[i] = i * block;
    }
    r[num] = n;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
    }
}

void update(int l, int r, int c) {
    if (belong[l] == belong[r]) {
        for (int i = l; i <= r; i++) {
            a[i] += c;
        }
        return;
    }
    for (int i = l; belong[i] == belong[l]; i++) {
        a[i] += c;
    }
    for (int i = r; belong[i] == belong[r]; i--) {
        a[i] += c;
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        more[i] += c;
    }
}

int query(int x) { 
	return a[x] + more[belong[x]]; 
}

int main() {
    while (~scanf("%d", &n)) {
        memset(more, 0, sizeof(more));
        memset(belong, 0, sizeof(belong));
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int f, l, r, c;
            scanf("%d%d%d%d", &f, &l, &r, &c);
            if (f == 0) {
                update(l, r, c);
            } else {
                printf("%d\n", query(r));
            }
        }
    }
    return 0;
}

LOJ 6278 区间加法更新 区间查询

需要 vector 数组进行区间排序,然后 lower_bound 二分找答案就行了。

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

const int maxn = 5e4 + 5;

int belong[maxn];
int block, num;
int l[maxn], r[maxn];
int a[maxn];
int more[maxn];
vector<int> v[maxn];
int n;

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block != 0) {
        num++;
    }
    for (int i = 1; i <= num; i++) {
        l[i] = (i - 1) * block + 1;
        r[i] = i * block;
    }
    r[num] = n;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
        v[belong[i]].push_back(a[i]);
    }
    for (int i = 1; i <= num; i++) {
        sort(v[i].begin(), v[i].end());
    }
}

void pushup(int x) {
    v[x].clear();
    for (int i = l[x]; i <= r[x]; i++) {
        v[x].push_back(a[i]);
    }
    sort(v[x].begin(), v[x].end());
}

void update(int l, int r, int c) {
    if (belong[l] == belong[r]) {
        for (int i = l; i <= r; i++) {
            a[i] += c;
        }
        pushup(belong[l]);
        return;
    }
    for (int i = l; belong[i] == belong[l]; i++) {
        a[i] += c;
    }
    pushup(belong[l]);
    for (int i = r; belong[i] == belong[r]; i--) {
        a[i] += c;
    }
    pushup(belong[r]);
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        more[i] += c;
    }
}

int query(int l, int r, int c) {
    int ans = 0;
    if (belong[l] == belong[r]) {
        for (int i = l; i <= r; i++) {
            if (a[i] + more[belong[i]] < c) {
                ans++;
            }
        }
        return ans;
    }
    for (int i = l; belong[i] == belong[l]; i++) {
        if (a[i] + more[belong[i]] < c) {
            ans++;
        }
    }
    for (int i = r; belong[i] == belong[r]; i--) {
        if (a[i] + more[belong[i]] < c) {
            ans++;
        }
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        int temp = c - more[i];
        ans += lower_bound(v[i].begin(), v[i].end(), temp) - v[i].begin();
    }
    return ans;
}

int main() {
    while (~scanf("%d", &n)) {
        memset(more, 0, sizeof(more));
        memset(belong, 0, sizeof(belong));
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int f, l, r;
            int c;
            scanf("%d%d%d%d", &f, &l, &r, &c);
            if (f == 0) {
                update(l, r, c);
            } else {
                printf("%d\n", query(l, r, c * c));
            }
        }
    }
    return 0;
}

LOJ 6279 区间加法更新 区间查询

几乎和第二题没差,就不上代码了。

LOJ 6280 区间加法更新 区间查询

几乎和第一题没差,需要维护每个块的和 s 数组,查询区间和。

LOJ 6281 区间开方更新 区间查询

这题相对前几题来说要难上不少,刚开始做的时候没反应过来,后来开方操作最多只会做64次,那么就肯定可以暴力了,不过需要标记整个块里是不是只有 0或1 (不需要开方操作了)。然后就是正常的区间加法,毕竟分块就是暴力哇。

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

const int maxn = 5e4 + 5;

int belong[maxn];
int L[maxn], R[maxn];
int a[maxn];
int n;
int block, num;
int s[maxn];
int mark[maxn];  // 标记 1 为区间中只有 0 或 1

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block) {
        num++;
    }
    for (int i = 1; i <= num; i++) {
        L[i] = (i - 1) * block + 1;
        R[i] = i * block;
    }
    R[num] = n;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
        s[belong[i]] += a[i];
    }
}

void update(int l, int r) {
    if (belong[l] == belong[r]) {
        if (mark[belong[l]] == 0) {
            mark[belong[l]] = 1;
            for (int i = l; i <= r; i++) {
                s[belong[i]] -= a[i];
                a[i] = sqrt(a[i]);
                s[belong[i]] += a[i];
            }
            for (int i = L[belong[l]]; i <= R[belong[l]]; i++) {
                if (a[i] > 1)
                    mark[belong[l]] = 0;
            }
        }
        return;
    }
    if (mark[belong[l]] == 0) {
        mark[belong[l]] = 1;
        for (int i = l; belong[i] == belong[l]; i++) {
            s[belong[i]] -= a[i];
            a[i] = sqrt(a[i]);
            s[belong[i]] += a[i];
        }
        for (int i = L[belong[l]]; i <= R[belong[l]]; i++) {
            if (a[i] > 1)
                mark[belong[l]] = 0;
        }
    }
    if (mark[belong[r]] == 0) {
        mark[belong[r]] = 1;
        for (int i = r; belong[i] == belong[r]; i--) {
            s[belong[i]] -= a[i];
            a[i] = sqrt(a[i]);
            s[belong[i]] += a[i];
        }
        for (int i = L[belong[r]]; i <= R[belong[r]]; i++) {
            if (a[i] > 1)
                mark[belong[r]] = 0;
        }
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        if (mark[i] == 1)
            continue;
        s[i] = 0;
        mark[i] = 1;
        for (int j = L[i]; j <= R[i]; j++) {
            a[j] = sqrt(a[j]);
            s[i] += a[j];
            if (a[j] > 1)
                mark[i] = 0;
        }
    }
}

int query(int l, int r) {
    int ans = 0;
    if (belong[l] == belong[r]) {
        for (int i = l; i <= r; i++) {
            ans += a[i];
        }
        return ans;
    }
    for (int i = l; belong[i] == belong[l]; i++) {
        ans += a[i];
    }
    for (int i = r; belong[i] == belong[r]; i--) {
        ans += a[i];
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        ans += s[i];
    }
    return ans;
}

int main() {
    while (~scanf("%d", &n)) {
        memset(mark, 0, sizeof(mark));
        memset(belong, 0, sizeof(belong));
        memset(s, 0, sizeof(s));
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int f, l, r, c;
            scanf("%d%d%d%d", &f, &l, &r, &c);
            if (f == 0) {
                update(l, r);
            } else {
                printf("%d\n", query(l, r));
            }
        }
    }
    return 0;
}

LOJ 6282 单点插入更新 单点查询

每次在 l 位置前插入 r 数值,其实这是个 rope 裸题,stl 天下第一。那既然这是个分块专题,就得用分块做。

这题的难点就是插入达到\(\sqrt{n}\)后进行重新分块,不然会超时(比如一直往这块里添加)。

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

const int maxn = 1e5 + 5;

int n;
int belong[maxn];
int a[maxn];
int block, num;
vector<int> v[maxn];
int temp[maxn << 2];
int cnt;

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block) {
        num++;
    }
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
        v[belong[i]].push_back(a[i]);
    }
}

void re_build() {
    cnt = 0;
    int tot = 0;
    for (int i = 1; i <= num; i++) {
        for (auto it = v[i].begin(); it != v[i].end(); it++) {
            temp[tot++] = *it;
        }
        v[i].clear();
    }
    block = sqrt(tot);
    for (int i = 0; i < tot; i++) {
        v[(i - 1) / block + 1].push_back(temp[i]);
    }
    num = tot / block;
    if (tot % block)
        num++;
}

void query(int x, int &q, int &t) {
    for (int i = 1; i <= num; i++) {
        if (x > (int)v[i].size()) {
            x -= (int)v[i].size();
        } else {
            q = i;
            t = x - 1;
            return;
        }
    }
}

void update(int l, int r) {
    cnt++;
    int q = -1, t = -1;
    query(l, q, t);
    v[q].insert(v[q].begin() + t, r);
    if (cnt == block) {
        re_build();
    }
    return;
}

int main() {
    while (~scanf("%d", &n)) {
        cnt = 0;
        for (int i = 0; i <= n; i++) {
            v[i].clear();
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int f, l, r, c;
            scanf("%d%d%d%d", &f, &l, &r, &c);
            if (f == 0) {
                update(l, r);
            } else {
                int q = -1, t = -1;
                query(r, q, t);
                printf("%d\n", v[q][t]);
            }
        }
    }
    return 0;
}

LOJ 6283 区间加法+乘法更新 单点查询

这题比较麻烦,但是不算难,就是写起来烦,需要多维护一个数组,乘法分配律就完事了。然后我 memset 赋 1 debug 了半小时...

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 10007;

const int maxn = 1e5 + 5;

int n;
int block, num;
ll a[maxn];
ll belong[maxn];
ll more[maxn];
ll s[maxn];
int L[maxn], R[maxn];

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block) {
        num++;
    }
    for (int i = 1; i <= num; i++) {
        L[i] = (i - 1) * block + 1;
        R[i] = i * block;
    }
    R[num] = n;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
    }
}

void pushup(int x) {
    for (int i = L[x]; i <= R[x]; i++) {
        a[i] = (a[i] * s[x] + more[x]) % mod;
    }
    s[x] = 1;
    more[x] = 0;
}

void update_add(int l, int r, ll c) {
    if (belong[l] == belong[r]) {
        pushup(belong[l]);
        for (int i = l; i <= r; i++) {
            a[i] = (a[i] + c) % mod;
        }
        return;
    }
    pushup(belong[l]);
    for (int i = l; belong[i] == belong[l]; i++) {
        a[i] = (a[i] + c) % mod;
    }
    pushup(belong[r]);
    for (int i = r; belong[i] == belong[r]; i--) {
        a[i] = (a[i] + c) % mod;
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        more[i] = (more[i] + c) % mod;
    }
}

void update_mul(int l, int r, ll c) {
    if (belong[l] == belong[r]) {
        pushup(belong[l]);
        for (int i = l; i <= r; i++) {
            a[i] = (a[i] * c) % mod;
        }
        return;
    }
    pushup(belong[l]);
    for (int i = l; belong[i] == belong[l]; i++) {
        a[i] = (a[i] * c) % mod;
    }
    pushup(belong[r]);
    for (int i = r; belong[i] == belong[r]; i--) {
        a[i] = (a[i] * c) % mod;
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        more[i] = (more[i] * c) % mod;
        s[i] = (s[i] * c) % mod;
    }
}

ll query(int x) { return a[x] * s[belong[x]] + more[belong[x]]; }

int main() {
    while (~scanf("%d", &n)) {
        for (int i = 0; i <= n; i++) {
            s[i] = 1;
        }
        memset(more, 0, sizeof(more));
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int f, l, r;
            ll c;
            scanf("%d%d%d%lld", &f, &l, &r, &c);
            if (f == 0) {
                update_add(l, r, c);
            } else if (f == 1) {
                update_mul(l, r, c);
            } else {
                printf("%lld\n", query(r) % mod);
            }
        }
    }
    return 0;
}

LOJ 6284 区间查询后修改

先查询 [l, r] 中有多少 c ,在把中间的数都改成 c 。多开一个 vis 数组,如果块中的所有数都并非都一样,则置 vis[x] = -1,此时需要强制暴力找;否则 vis 数组记录的就是整个块中的数是多少,满足条件就直接 ans += block 。

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

const int maxn = 1e5 + 5;

int belong[maxn];
int n, block, num;
int a[maxn];
int L[maxn], R[maxn];
int vis[maxn];

void build() {
    block = sqrt(n);
    num = n / block;
    if (n % block) {
        num++;
    }
    for (int i = 1; i <= num; i++) {
        L[i] = (i - 1) * block + 1;
        R[i] = i * block;
    }
    R[num] = n;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
    }
}

void pushup(int x) {
    if (vis[x] == -1)
        return;
    for (int i = L[x]; i <= R[x]; i++) {
        a[i] = vis[x];
    }
    vis[x] = -1;
}

int query(int l, int r, int c) {
    int ans = 0;
    if (belong[l] == belong[r]) {
        pushup(belong[l]);
        for (int i = l; i <= r; i++) {
            if (a[i] == c) {
                ans++;
            }
            a[i] = c;
        }
        return ans;
    }
    pushup(belong[l]);
    for (int i = l; belong[i] == belong[l]; i++) {
        if (a[i] == c) {
            ans++;
        }
        a[i] = c;
    }
    pushup(belong[r]);
    for (int i = r; belong[i] == belong[r]; i--) {
        if (a[i] == c) {
            ans++;
        }
        a[i] = c;
    }
    for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
        if (vis[i] == -1) {
            for (int j = L[i]; j <= R[i]; j++) {
                if (a[j] == c) {
                    ans++;
                }
            }
        } else {
            if (vis[i] == c) {
                ans += block;
            }
        }
        vis[i] = c;
    }
    return ans;
}

int main() {
    while (~scanf("%d", &n)) {
        memset(belong, 0, sizeof(belong));
        memset(vis, -1, sizeof(vis));
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        build();
        for (int i = 1; i <= n; i++) {
            int l, r, c;
            scanf("%d%d%d", &l, &r, &c);
            printf("%d\n", query(l, r, c));
        }
    }
    return 0;
}

LOJ 6285 区间查询众数

这题...就很麻烦了,需要先预处理出每个块内的众数,然后再用 upper_bound - lower_bound 去得到某个数的个数。总之说起来挺复杂的,看着代码理解一下还是可以的。

吐槽一下讨论版玄学,这啥数据啊,块的大小还得是 30 ,我代码都重构的不成样子了...

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

const int maxn = 1e5 + 5;

int n;
int block, num;
int belong[maxn];
int a[maxn];
map<int, int> mp;                // a[i] 是第几个出现的
int val[maxn];                   // val[id] = a[i]
int mark[maxn / 30][maxn / 30];  // 预处理每个整块的众数
int cnt[maxn];
vector<int> v[maxn];  // 记录每个数出现的每一个位置

/*void build() {
}*/

inline int get_id(int l, int r, int x) {
    return upper_bound(v[x].begin(), v[x].end(), r) - lower_bound(v[x].begin(), v[x].end(), l);
}

inline int query(int l, int r) {
    int MAX = 0, tot = 0;
    if (belong[l] == belong[r]) {
        for (int i = l; i <= r; i++) {
            int temp = get_id(l, r, a[i]);
            if (temp > tot || (temp == tot && val[a[i]] < val[MAX])) {
                tot = temp;
                MAX = a[i];
            }
        }
        return MAX;
    }
    for (int i = l; belong[i] == belong[l]; i++) {
        int temp = get_id(l, r, a[i]);
        if (temp > tot || (temp == tot && val[a[i]] < val[MAX])) {
            tot = temp;
            MAX = a[i];
        }
    }
    for (int i = r; belong[i] == belong[r]; i--) {
        int temp = get_id(l, r, a[i]);
        if (temp > tot || (temp == tot && val[a[i]] < val[MAX])) {
            tot = temp;
            MAX = a[i];
        }
    }
    int res = mark[belong[l] + 1][belong[r] - 1];
    int temp = get_id(l, r, res);
    if (temp > tot || (temp == tot && val[res] < val[MAX])) {
        tot = temp;
        MAX = res;
    }
    return MAX;
}

int main() {
    scanf("%d", &n);
    int id = 0;
    block = 30;
    num = n / block;
    if (n % block) {
        num++;
    }
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        if (mp[a[i]] == 0) {
            mp[a[i]] = ++id;
            val[id] = a[i];
        }
        belong[i] = (i - 1) / block + 1;
        a[i] = mp[a[i]];
        v[a[i]].push_back(i);
    }
    for (int i = 1; i <= num; i++) {
        memset(cnt, 0, sizeof(cnt));
        int MAX = 0, tot = 0;
        for (int j = (i - 1) * block + 1; j <= n; j++) {
            int temp = ++cnt[a[j]];
            if (temp > tot || (temp == tot && val[a[j]] < val[MAX])) {
                tot = temp;
                MAX = a[j];
            }
            mark[i][belong[j]] = MAX;
        }
    }
    for (int i = 1; i <= n; i++) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", val[query(l, r)]);
    }
    return 0;
}

总结一下分块,那就是暴力哇,惹不起惹不起.jpg,溜了溜了。

posted @ 2019-05-25 23:42  Decray  阅读(281)  评论(0编辑  收藏  举报