zoukankan      html  css  js  c++  java
  • 数列分块入门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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", val[query(l, r)]);
        }
        return 0;
    }
    

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

  • 相关阅读:
    Ubuntu常用命令
    Linux 虚拟机安装Ubuntu
    word 2019 方框中打勾
    apache httpd的安装和虚拟主机配置(基于centos 7)
    LoadRunner 12.5 community edition 试玩+Linux主机监控
    Directory Service目录服务
    TCP/IP脑图
    Qt样式选择器
    常见电脑进入BIOS的快捷键
    网线(RJ45接口)的接法
  • 原文地址:https://www.cnblogs.com/Decray/p/10924593.html
Copyright © 2011-2022 走看看