分块学习心得

首先想必不用我介绍分块是一种很优雅的暴力算法。它的思想比较好理解,但是在实现的过程中还是比较复杂的。

顾名思义分块就是把一个数列分成n快,每个块的大小为根号n。

 dis = sqrt(a);               //确定块的大小
    num = ceil(1.0 * a / dis);       // 确定有多少个块
    for(int i = 1;i <= num;i ++) {     //处理出每个块的左右边界(相邻块的左右边界是不重复的)
        l[i] = (i - 1) * dis + 1;
        r[i] = i * dis;
    }
    r[num] = a;                   // 特殊处理最右边界可能最后一个块的大小会小于根号n
    for(int i = 1;i <= a;i ++) {
        scanf("%d",&val[i]);
        b[i] = val[i];
        be[i] = (i - 1) / dis + 1;   //  确定每一个数属于哪一个块。
    }

接下来讲讲分块的操作。

(1) 区间加减。

思路和线段树类似在修改的时候遇到完整的区间就用lazy标记。不完整的区间(一头一尾)就暴力修改。

void add(int ll,int rr,int vall) {
        if(be[ll] == be[rr])         //如果两个边界都属于一个快就暴力修改
            for(int i = ll;i <= rr;i ++)
                val[i] += vall;
        else {
            for (int i = ll; i <= r[be[ll]] ; ++i)    //接下来的就是一般的情况一头一尾多出来了
                val[i] += vall;               // 从起点开始到r[be[ll]]指最左边多出来的那个快的右节点。
            for (int i = be[ll] + 1; i <= be[rr] - 1; ++i)
                tag[i] += vall;          // 中间完整的块就是用tag加。
            for (int i = l[be[rr]]; i <= rr; ++i)
                val[i] += vall;         //  右区间同理属于尾巴上多出来的从终点所属区间的左节点开始暴力修改。
        }
}

查询操作同理下面会贴代码。

入门建议刷一下#6277. 数列分块入门 1 - 题目 - LibreOJ (loj.ac)

接下来就是稍微复杂点的区间内求小于某个数的个数。

(2)区间内求小于K的数的个数。 题目:#6278. 数列分块入门 2 - 题目 - LibreOJ (loj.ac)

思路:思路还是很简单的,我们把每个块内进行排序。遇到完整快的时候就用lowerbound查询。非完整块的时候就暴力查找。

初始化:

dis = sqrt(a);
    num = ceil(1.0 * a / dis);
    for(int i = 1;i <= a;i ++) {
        scanf("%d",&val[i]);
        b[i] = val[i];
        be[i] = (i - 1) / dis + 1;
    }
    for(int i = 1; i <= num;i ++) {
        l[i] = (i - 1) * dis + 1;
        r[i] = i * dis;
    }
    r[num] = a;
    for(int i = 1 ;i <= num;i ++) {         //按照块进行排序。
        sort(b + l[i],b + r[i] + 1);
    }

修改

void add(int ll,int rr,int z) {
    int pol = be[ll];
    int por = be[rr];
    if(pol == por) {
        for(int i = ll;i <= rr;i ++)
            val[i] += z;                     // 首先要对原序列修改
        for(int i = l[pol];i <= r[por];i ++)
            b[i] = val[i];                   //因为b进行了排序所以不能修改b的值,需要将完整的块赋值。
        sort(b + l[pol],b + r[por] + 1);     // 重新排序。
    }
    else {
        for(int i = ll;i <= r[pol];i ++)
            val[i] += z;                      // 非完整的块的道理同上。
        for(int i = l[pol];i <= r[pol];i ++)
            b[i] = val[i];
        sort(b + l[pol],b + r[pol] + 1);
        for(int i = pol + 1;i <= por - 1;i ++)
            laz[i] += z;                      // 完整的块直接加标记,不会影响他的排序。
        for(int i = l[por];i <= rr;i ++)
            val[i] += z;
        for(int i = l[por];i <= r[por];i ++)
            b[i] = val[i];
        sort(b + l[por],b + r[por] + 1);
    }
}

查询操作就要复杂一点

int qu(int ll,int rr,int vall) {
    int cnt = 0;
    int pol = be[ll];
    int por = be[rr];
    if(pol == por) {
        for (int i = ll; i <= rr; ++i) {
            if(vall > val[i] + laz[pol])
                cnt ++;                     // 对于特殊情况的处理。
        }
        return cnt;
    }
    else {
        for(int i = ll;i <= r[pol];i ++)
            if(vall > val[i] + laz[pol])  // 先暴力处理一头一尾部分,注意在处理一头一尾是需要使用原数组。
                cnt ++;
        for (int i = l[por]; i <= rr; ++i) {
            if(vall > val[i] + laz[por])
                cnt ++;
        }
        for(int i = pol + 1;i <= por - 1;i ++) {  //中间部分用lowerbound查询。
            cnt += lower_bound(b + l[i],b + r[i] + 1,vall - laz[i]) - (b + l[i]);
        }
        return cnt;
    }
}

还有一道题和上题类似也可以练一下:LibreOJ (loj.ac)

我直接放代码 

#include <bits/stdc++.h>
#define N 100010
using namespace std;
int l[N],r[N],be[N],val[N],b[N],laz[N];
int num,dis;
void add(int ll,int rr,int vall) {
    int pol = be[ll];
    int por = be[rr];
    if(pol == por) {
        for(int i = ll;i <= rr;i ++ )
            val[i] += vall;
        for(int i = l[pol];i <= r[por];i ++)
            b[i] = val[i];
        sort(b + l[pol],b + r[pol] + 1);
    }
    else {
        for(int i = ll;i <= r[pol];i ++)
            val[i] += vall;
        for(int i = l[pol];i <= r[pol];i ++)
            b[i] = val[i];
        sort(b + l[pol],b + r[pol] + 1);
        for(int i = pol + 1;i <= por - 1;i ++)
            laz[i] += vall;
        for(int i = l[por];i <= rr;i ++)
            val[i] += vall;
        for(int i = l[por];i <= r[por];i ++)
            b[i] = val[i];
        sort(b + l[por],b + r[por] + 1);
    }
}
int qu(int ll,int rr,int vall) {
    int pol = be[ll];
    int por = be[rr];
    int maxx = -1;
    if(pol == por) {
        for(int i = ll;i <= rr;i ++)
            if(val[i] + laz[pol] < vall)
                maxx = max(maxx,val[i] + laz[pol]);
        return maxx;
    }
    else {
        for(int i = ll;i <= r[pol];i ++)
            if(val[i] + laz[pol] < vall)
                maxx = max(maxx,val[i] + laz[be[i]]);
        for(int i = l[por];i <= rr;i ++)
            if(val[i] + laz[por] < vall)
                maxx = max(maxx,val[i] + laz[be[i]]);
        for(int i = pol + 1;i <= por - 1;i ++) {
            int tmp = lower_bound(b + l[i],b + r[i] + 1,vall - laz[i]) - (b + l[i]);
            if(tmp == 0) {
                continue;
            }
            maxx = max(maxx,b[l[i] + tmp - 1] + laz[i]);
        }
        return maxx;
    }
}
int main() {
    int a;
    cin>>a;
    dis = sqrt(a);
    num = ceil(1.0 * a / dis);
    for(int i = 1;i <= num;i ++) {
        l[i] = (i - 1) * dis + 1;
        r[i] = i * dis;
    }
    r[num] = a;
    for(int i = 1;i <= a;i ++) {
        scanf("%d",&val[i]);
        b[i] = val[i];
        be[i] = (i - 1) / dis + 1;
    }
    for(int i = 1;i <= num;i ++) {
        sort (b + l[i],b + r[i] + 1);
    }
    int op,ll,rr,vall;
    for(int i = 1;i <= a;i ++) {
        cin>>op>>ll>>rr>>vall;
        if(op == 0 ) {
            add(ll,rr,vall);
        }
        else {
            cout<<qu(ll,rr,vall)<<endl;
        }
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值