优雅的暴力---数列分块入门

概述

区间问题一般都十分灵活,可以用线段树来解决,虽然时间复杂度可以达到 O ( l o g ( n ) ) O(log(n)) O(log(n)),但是有时候不太好写,分块可以让时间复杂度达到 O ( n ) O(\sqrt n) O(n ),虽然不及线段树,但是应用范围也很广,也比较简单,很有学习的必要

  • 分块思想很简单,一般把一个区间分成 n \sqrt n n 块,如下图,如果有剩余部分,这样我们就多加一块
    在这里插入图片描述
  • 然后比如说处理一个区间 [ L , R ] [L,R] [L,R],如果是这样
    在这里插入图片描述
  • 那么我们对于 [ L , 2 ] [L,2] [L,2] [ 4 , R ] [4,R] [4,R]这两块暴力处理,中间两个大块的区间整体处理,这就是分块的思想,那么就介绍完了,具体实现过程见例题

数列分块九题

https://loj.ac/p?keyword=%E6%95%B0%E5%88%97%E5%88%86%E5%9D%97

1. 区间加,单点查询

  • 设每块的大小为 b l o c k block block,那么块数应该是 ⌈ n b l o c k ⌉ \lceil \frac{n}{block}\rceil blockn,然后我们使用 b e l o n g [ i ] belong[i] belong[i]记录 i i i节点在哪个块中, L [ i ] , R [ i ] L[i],R[i] L[i],R[i]分别表示 i i i块左端点和右端点位置,具体见代码,思路很清晰,这也是树状数组和线段树的入门题
  • 更新的时候注意需要检查是不是在一个块中,如果在一个块中那么直接暴力更新,否则两端暴力,中间完整部分按照块更新
  • 注意右端点要和 n n n取一个最小值,因为右端点可能越界,主要是最后一块的问题
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1), belong(n + 1);
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        belong[i] = (i - 1) / block + 1;
    }
    vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
    }
    for(int i=0;i<n;i++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int, int)> modify = [&](int l, int r, int c){
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        a[i] += c;
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        a[i] += c;
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        lazy[i] += c;
                    }                    
                }
            };
            modify(l, r, c);
        }else{
            function<int(int)> query = [&](int r){
                return a[r] + lazy[belong[r]];
            };
            cout << query(r) << '\n';
        }
    }
    return 0;
}

2. 区间加,区间小于某个数的数的个数

  • 这也是一个类型题,区间加我们可以暴力,那么怎么统计区间小于某个数的个数呢?处理方法是使用另外一个数组,维护区间的单调性,然后在这个数组里面二分找小于 c c c的数的个数,看起来好像很慢,每一次更新都要把暴力修改过的块赋值给 d d d数组,而且每次都要排个序,但是其实挺快的,时间复杂度 n n n\sqrt n nn ,这题数据 5 e 4 5e4 5e4,没问题,可能 1 e 6 1e6 1e6就不太行了
  • 二分手写吧,很好写
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> a(n + 1), belong(n + 1), d(n + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        d[i] = a[i];
        belong[i] = (i - 1) / block + 1;
    }
    vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
        sort(d.begin() + L[i], d.begin() + R[i] + 1);
    }
    for(int i=0;i<n;i++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int, int)> modify = [&](int l, int r, int c){
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[l]];i<=R[belong[l]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[l]];i<=R[belong[l]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
                    for(int i=L[belong[r]];i<=r;i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[r]];i<=R[belong[r]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[r]], d.begin() + R[belong[r]] + 1);
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        lazy[i] += c;
                    }
                }
            };
            modify(l, r, c);
        }else{
            function<int(int, int, 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] + lazy[belong[i]] < c) ans += 1;
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        if(a[i] + lazy[belong[i]] < c) ans += 1;
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        if(a[i] + lazy[belong[i]] < c) ans += 1;
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        int x = L[i];
                        int y = R[i];
                        int res = 0;
                        while(x <= y){
                            int mid = (y - x >> 1) + x;
                            if(d[mid] + lazy[i] < c){
                                res = mid + 1 - L[i];
                                x = mid + 1;
                            }else{
                                y = mid - 1;
                            }
                        }
                        ans += res;
                    }
                }
                return ans;
            };
            cout << query(l, r, c * c) << '\n';
        }
    }
    return 0;
}

3. 区间加,求前驱

  • 上一次求前驱还是在学 t r e a p treap treap树的时候,虽然现在忘得差不多了,洛谷上好像有道二叉树的题也是求前驱等等这一套东西的,求前驱的方法很多,线段树、平衡树、普通二叉树都能做,现在我们试试使用分块来解决这个问题
  • 某个数的前驱就是小于这个数的最大值,那么我们仍然用第二题的思路,用一个有序数组,然后对于一整块的元素,在这个数组里面二分查找,两侧小块部分直接暴力,如果都找不到就返回 − 1 -1 1,注意 l a z y lazy lazy标记别忘了
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> a(n + 1), belong(n + 1), d(n + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        d[i] = a[i];
        belong[i] = (i - 1) / block + 1;
    }
    vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
        sort(d.begin() + L[i], d.begin() + R[i] + 1);
    }
    for(int i=0;i<n;i++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int, int)> modify = [&](int l, int r, int c){
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[l]];i<=R[belong[l]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[l]];i<=R[belong[l]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
                    for(int i=L[belong[r]];i<=r;i++){
                        a[i] += c;
                    }
                    for(int i=L[belong[r]];i<=R[belong[r]];i++){
                        d[i] = a[i];
                    }sort(d.begin() + L[belong[r]], d.begin() + R[belong[r]] + 1);
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        lazy[i] += c;
                    }
                }
            };
            modify(l, r, c);
        }else{
            function<int(int, int, int)> query = [&](int l, int r, int c){
                int ans = INT_MIN;
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        if(a[i] + lazy[belong[i]] < c){
                            ans = max(a[i] + lazy[belong[i]], ans);
                        }
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        if(a[i] + lazy[belong[i]] < c){
                            ans = max(ans, a[i] + lazy[belong[i]]);
                        }
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        if(a[i] + lazy[belong[i]] < c){
                            ans = max(ans, a[i] + lazy[belong[i]]);
                        }
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        int x = L[i];
                        int y = R[i];
                        while(x <= y){
                            int mid = (y - x >> 1) + x;
                            if(d[mid] + lazy[i] < c){
                                ans = max(ans, d[mid] + lazy[i]);
                                x = mid + 1;
                            }else{
                                y = mid - 1;
                            }
                        }
                    }
                }
                return (ans == INT_MIN ? -1 : ans);
            };
            cout << query(l, r, c) << '\n';
        }
    }
    return 0;
}

4. 区间加,区间求和

  • 用一个数组维护块内和,更新的时候同时更新这个数组,思路简单,细节需要注意,且需要开long long
#include <bits/stdc++.h>

using namespace std;

#define int long long
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1), belong(n + 1);
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> L(tot + 1), R(tot + 1), sum(tot + 1), lazy(tot + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        belong[i] = (i - 1) / block + 1;
        sum[belong[i]] += a[i];
    }
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(i * block, n);
    }
    for(int i=0;i<n;i++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int, int)> modify = [&](int l, int r, int c){
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        a[i] += c;
                        sum[belong[i]] += c;
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        a[i] += c;
                        sum[belong[i]] += c;
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        a[i] += c;
                        sum[belong[i]] += c;
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        sum[i] += (R[i] - L[i] + 1) * c;
                        lazy[i] += c;
                    }
                }
            };
            modify(l, r, c);
        }else{
            function<int(int, int, int)> query = [&](int l, int r, int c){
                int ans = 0;
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        ans += a[i];
                        if(ans >= c) ans %= c;
                        ans += lazy[belong[i]];
                        if(ans >= c) ans %= c;
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        ans += a[i];
                        if(ans >= c) ans %= c;
                        ans += lazy[belong[i]];
                        if(ans >= c) ans %= c;
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        ans += a[i];
                        if(ans >= c) ans %= c;
                        ans += lazy[belong[i]];
                        if(ans >= c) ans %= c;
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        ans += sum[i];
                        if(ans >= c) ans %= c;
                    }
                }
                return ans;
            };
            cout << query(l, r, c + 1) << '\n';
        }
    }
    return 0;
}

5. 区间开方,区间查询

  • 区间开方的的一个思路是区间开方的总次数会很少,因为一直开方取整下去不久就会变1, 1 e 9 1e9 1e9不停开方五次就会变1;如果原来是0那么不变,所以可以考虑维护一个区间最值,分块处理就是维护每个块的最值,如果发现块最值是 0 0 0或者 1 1 1,那么这个块就不用再开方了,因为不会再改变
  • 然后再维护一个块内和,后面的处理比较简单,关键在于开方维护这里,具体见代码
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> a(n + 1), belong(n + 1);
    vector<int> maxn(tot + 1), sum(tot + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        belong[i] = (i - 1) / block + 1;
        sum[belong[i]] += a[i];
        maxn[belong[i]] = max(maxn[belong[i]], a[i]);
    }
    vector<int> L(tot + 1), R(tot + 1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
    }
    for(int k=0;k<n;k++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int)> modify = [&](int l, int r){
                if(belong[l] == belong[r]){
                    if(maxn[belong[l]] == 0 || maxn[belong[l]] == 1) return;
                    for(int i=l;i<=r;i++){
                        int past = a[i];
                        a[i] = sqrt(a[i]);
                        sum[belong[i]] -= past - a[i];
                    }
                    int mx = 0;
                    for(int i=L[belong[l]];i<=R[belong[l]];i++){
                        mx = max(mx, a[i]);
                    }
                    maxn[belong[l]] = mx;
                }else{
                    if(maxn[belong[l]] != 0 && maxn[belong[l]] != 1){
                        for(int i=l;i<=R[belong[l]];i++){
                            int past = a[i];
                            a[i] = sqrt(a[i]);
                            sum[belong[i]] -= past - a[i];
                        }
                        int mx = 0;
                        for(int i=L[belong[l]];i<=R[belong[l]];i++){
                            mx = max(mx, a[i]);
                        }
                        maxn[belong[l]] = mx;
                    }
                    if(maxn[belong[r]] != 0 && maxn[belong[r]] != 1){
                        for(int i=L[belong[r]];i<=r;i++){
                            int past = a[i];
                            a[i] = sqrt(a[i]);
                            sum[belong[i]] -= past - a[i];
                        }
                        int mx = 0;
                        for(int i=L[belong[r]];i<=R[belong[r]];i++){
                            mx = max(mx, a[i]);
                        }
                        maxn[belong[r]] = mx;
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        if(maxn[i] == 0 || maxn[i] == 1) continue;
                        int mx = 0;
                        for(int j=L[i];j<=R[i];j++){
                            int past = a[j];
                            a[j] = sqrt(a[j]);
                            sum[i] -= past - a[j];
                            mx = max(mx, a[j]);
                        }
                        maxn[i] = mx;
                    }
                }
            };
            modify(l, r);
        }else{
            function<int(int, int)> query = [&](int l, int r){
                int ans = 0;
                if(belong[l] == belong[r]){
                    for(int i=l;i<=r;i++){
                        ans += a[i];
                    }
                }else{
                    for(int i=l;i<=R[belong[l]];i++){
                        ans += a[i];
                    }
                    for(int i=L[belong[r]];i<=r;i++){
                        ans += a[i];
                    }
                    for(int i=belong[l]+1;i<=belong[r]-1;i++){
                        ans += sum[i];
                    }
                }
                return ans;
            };
            cout << query(l, r) << '\n';
        }
    }
    return 0;
}

6. 单点插入,单点询问

  • 这个操作使用平衡树最简单。使用分块思路是设置每个块类型为vector,每次暴力插入,对吧超级暴力,但是这会带来一个问题,如果有某一块插入了过多的元素,那么分块就会退化成更加普通的暴力,解决这个问题的办法是对块的大小进行限制,如果超过了预定的大小,那么就重新分块,这个大小可以乱搞,但是比较理性的大小选择是 2 n \sqrt{2n} 2n ,其中 n n n表示元素的数量,注意这里 n n n的大小是变化之中的
  • 这样分块的效果是,插入时间复杂度是 O ( n ) O(\sqrt{n}) O(n ),有 n n n次插入,某一个块插入次数为 n \sqrt{n} n 的时候就进行重新排布,重新分块的时间复杂度是 O ( n ) O(n) O(n)的,这样均摊时间复杂度是 O ( n n ) O(n\sqrt{n}) O(nn )

未经过重新分块程序如下

#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<vector<int> > b(tot + 1);
    vector<int> belong(n + 1);
    for(int i=1;i<=n;i++){
        int x;
        cin >> x;
        belong[i] = (i - 1) / block + 1;
        b[belong[i]].push_back(x);
    }
    int q = n;
    while(q--){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int)> modify = [&](int l, int r){
                int pt = 1;
                int sz = b[pt].size();
                while(sz < l){
                    pt += 1;
                    sz += b[pt].size();
                }
                b[pt].insert(b[pt].begin() + l - (sz - b[pt].size()) - 1, r);
            };
            modify(l, r);
        }else{
            function<int(int)> query = [&](int r){
                int pt = 1;
                int sz = b[pt].size();
                while(sz < r){
                    pt += 1;
                    sz += b[pt].size();
                }
                return b[pt][r - (sz - b[pt].size()) - 1];
            };
            cout << query(r) << '\n';
        }
    }
    return 0;
}

重新分块程序如下

#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<vector<int> > b(tot + 1);
    vector<int> belong(n + 1);
    for(int i=1;i<=n;i++){
        int x;
        cin >> x;
        belong[i] = (i - 1) / block + 1;
        b[belong[i]].push_back(x);
    }
    int q = n;
    function<void()> Rebuild = [&](){
        block = sqrt(n);
        vector<int> a(n + 1);
        int top = 0;
        for(int i=1;i<=tot;i++){
            for(auto j : b[i]){
                a[++top] = j;
            }
            b[i].clear();
        }
        tot = n / block;
        if(n % block) tot += 1;
        belong.resize(top + 1);
        b.resize(tot + 1);
        for(int i=1;i<=top;i++){
            belong[i] = (i - 1) / block + 1;
            b[belong[i]].push_back(a[i]);
        }
    };
    while(q--){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        if(opt == 0){
            function<void(int, int)> modify = [&](int l, int r){
                int pt = 1;
                int sz = b[pt].size();
                while(sz < l){
                    pt += 1;
                    sz += b[pt].size();
                }
                b[pt].insert(b[pt].begin() + l - (sz - b[pt].size()) - 1, r);
                n += 1;
                if(b[pt].size() > 2 * block) Rebuild();
            };
            modify(l, r);
        }else{
            function<int(int)> query = [&](int r){
                int pt = 1;
                int sz = b[pt].size();
                while(sz < r){
                    pt += 1;
                    sz += b[pt].size();
                }
                return b[pt][r - (sz - b[pt].size()) - 1];
            };
            cout << query(r) << '\n';
        }
    }
    return 0;
}
  • 从时间上看好像还变慢了,但是这样时间复杂度是对的,可能换用静态数组能更快一些

7. 区间乘法,区间加法,单点询问

  • 这也是一个高级数据结构的入门操作,加法我们之前已经处理过了,那么如果加法和乘法混合该怎么办呢?注意我们是单点询问,所以lazy标记是必须要有的,所以这里面我们需要两个lazy标记,分别表示加法和乘法
  • 这两种操作我们都需要考虑整块和非整块这两种情况,对于整块,如果是一个加法操作,那么我们直接加到addlazy上面即可,如果是一个乘法操作,我们不仅需要乘到mullazy上面,我们还需要乘到addlazy上面;对于非整块,我们肯定是要暴力更新,在这之前我们必须先把这一块的lazy标记都传递下来,先乘后加,因为加法的lazy标记我们已经乘法处理过了
#include <bits/stdc++.h>

using namespace std;

const int MOD = 1e4 + 7;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> a(n + 1);
    vector<int> belong(n + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        belong[i] = (i - 1) / block + 1;
    }
    vector<int> L(tot + 1), R(tot + 1);
    vector<int> addlazy(tot + 1), mulazy(tot + 1, 1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
    }   
    for(int i=0;i<n;i++){
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;
        function<void(int)> Push_Down = [&](int k){
            for(int i=L[belong[k]];i<=R[belong[k]];i++){
                a[i] = (a[i] * mulazy[belong[k]] % MOD + addlazy[belong[k]]) % MOD;
            }
            mulazy[belong[k]] = 1;
            addlazy[belong[k]] = 0;
        };
        if(opt == 0){
            function<void(int, int, int)> add = [&](int l, int r, int c){
                Push_Down(l);
                for(int i=l;i<=min(R[belong[l]], r);i++){
                    a[i] = (a[i] + c) % MOD;
                }
                if(belong[l] == belong[r]) return;
                Push_Down(r);
                for(int i=belong[l]+1;i<=belong[r]-1;i++){
                    addlazy[i] = (addlazy[i] + c) % MOD;
                }
                for(int i=L[belong[r]];i<=r;i++){
                    a[i] = (a[i] + c) % MOD;
                }
            };
            add(l, r, c);
        }else if(opt == 1){
            function<void(int, int, int)> mul = [&](int l, int r, int c){
                Push_Down(l);
                for(int i=l;i<=min(R[belong[l]], r);i++){
                    a[i] = (a[i] * c) % MOD;
                }
                if(belong[l] == belong[r]) return;
                Push_Down(r);
                for(int i=belong[l]+1;i<=belong[r]-1;i++){
                    addlazy[i] = (addlazy[i] * c) % MOD;
                    mulazy[i] = (mulazy[i] * c) % MOD;
                }
                for(int i=L[belong[r]];i<=r;i++){
                    a[i] = (a[i] * c) % MOD;
                }
            };
            mul(l, r, c);
        }else{
            function<int(int)> query = [&](int r){
                return (a[r] * mulazy[belong[r]] % MOD + addlazy[belong[r]]) % MOD;
            };
            cout << query(r) << '\n';
        }
    }
    return 0;
}

8. 区间先查询数的个数,再全改为一个数

  • 显然如果全改为一个数,我们可以给一整块做一个标记,这样查询到这个块的时候我们就可以使用这个信息,直接查询到结果,思路也是比较简单
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1), belong(n + 1);
    int block = sqrt(n);
    int tot = n / block;
    if(n % block) tot += 1;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        belong[i] = (i - 1) / block + 1;
    }
    vector<int> L(tot + 1), R(tot + 1);
    vector<int> vis(tot + 1, -1);
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
    }
    for(int i=0;i<n;i++){
        int l, r ,c;
        cin >> l >> r >> c;
        function<int(int, int, int)> query = [&](int l, int r, int c){
            int ans = 0;
            if(vis[belong[l]] != -1){
                for(int i=L[belong[l]];i<=R[belong[l]];i++){
                    a[i] = vis[belong[l]];
                }
            }
            for(int i=l;i<=min(r, R[belong[l]]);i++){
                if(a[i] == c) ans += 1;
                a[i] = c;
            }
            vis[belong[l]] = -1;
            if(belong[l] == belong[r]) return ans;
            if(vis[belong[r]] != -1){
                for(int i=L[belong[r]];i<=R[belong[r]];i++){
                    a[i] = vis[belong[r]];
                }
            }
            for(int i=L[belong[r]];i<=r;i++){
                if(a[i] == c) ans += 1;
                a[i] = c;
            }
            vis[belong[r]] = -1;
            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 += 1;
                    }
                }
                if(vis[i] == c){
                    ans += R[i] - L[i] + 1;
                }
                vis[i] = c;
            }
            return ans;
        };
        cout << query(l, r, c) << '\n';
    }
    return 0;
}

9. 求任意两点之间的区间众数

  • 假设一共有 t o t tot tot个块,我们使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从块 i i i j j j的区间最小众数,这个可以预处理出来,做法就是块之间的暴力,使用一些离散化的技巧就可以实现,具体看代码,这个时间复杂度是 O ( n n ) O(n\sqrt n) O(nn )
  • 这样我们来考虑怎么高效求 [ l , r ] [l,r] [l,r]的区间众数,首先预处理出来每个数所在的块和每个块的左端点右端点这些信息,然后开一个桶,每个桶是一个数,桶的标号需要离散化处理,把相同的数的标号放在桶里,那么得到的这个桶内部标号是增序排列的,这样我们就可以使用二分来求出一个区间内有多少个某个数了,接下来我们使用块内暴力,块间记录的方法就可以解出这个题
  • 这个题卡时间,常规的块大小为 n \sqrt n n T T T一个点,对于块大小的选择可以多试试,选择 n 2 \frac{\sqrt n}{2} 2n 作为块的大小可以通过,或者也可以使用一些常数,比如100,也能通过这个题
  • 最后注意一下我们要求的是区间众数的最小值
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    int block = sqrt(n) / 2;
    int tot = n / block;
    if(n % block) tot += 1;
    vector<int> a(n + 1);
    vector<int> belong(n + 1);
    unordered_map<int, int> mp;
    int tt = 0;
    vector<vector<int> > vs(n + 1);
    vector<int> val(1);
    vector<int> L(tot + 1);
    vector<int> R(tot + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        if(!mp.count(a[i])){
            mp[a[i]] = ++tt;
            val.push_back(a[i]);
        }
        a[i] = mp[a[i]];
        belong[i] = (i - 1) / block + 1;
        vs[a[i]].push_back(i);
    }
    vector<vector<int> > dp(tot + 1, vector<int> (tot + 1));
    function<int(int, int, int)> Get = [&](int l, int r, int x){
        return upper_bound(vs[x].begin(), vs[x].end(), r) - lower_bound(vs[x].begin(), vs[x].end(), l);
    };
    for(int i=1;i<=tot;i++){
        L[i] = (i - 1) * block + 1;
        R[i] = min(n, i * block);
        function<void(int)> init = [&](int x){
            vector<int> cnt(tt + 1);
            int mx = INT_MIN;
            int ans = 0;
            for(int j=(x - 1) * block + 1;j<=n;j++){
                cnt[a[j]] += 1;
                if(cnt[a[j]] > mx || (cnt[a[j]] == mx && val[a[j]] < val[ans])){
                    mx = cnt[a[j]];
                    ans = a[j];
                }
                dp[x][belong[j]] = ans;
            }
        };
        init(i);
    }
    for(int k=0;k<n;k++){
        int l, r;
        cin >> l >> r;
        function<int(int, int)> query = [&](int l, int r){
            if(belong[l] == belong[r]){
                int mx = 0;
                int ans = INT_MAX;
                for(int i=l;i<=r;i++){
                    int tmp = Get(l, r, a[i]);
                    if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
                        mx = tmp;
                        ans = a[i];
                    }
                }
                return val[ans];
            }else{
                int ans = dp[belong[l] + 1][belong[r] - 1];
                int mx = Get(l, r, ans);
                for(int i=l;i<=R[belong[l]];i++){
                    int tmp = Get(l, r, a[i]);
                    if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
                        mx = tmp;
                        ans = a[i];
                    }
                }
                for(int i=L[belong[r]];i<=r;i++){
                    int tmp = Get(l, r, a[i]);
                    if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
                        mx = tmp;
                        ans = a[i];
                    }
                }
                return val[ans];
            }
        };
        cout << query(l, r) << '\n';
    }
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值