【暖*墟】 #数据结构# 分块入门训练题1~9

引入 foreword

数列分块就是把数列中【每m个元素打包起来】,达到优化算法的目的。

把每m个元素分为一块,共有n/m块,区间修改涉及O(n/m)个整块,以及两侧两个不完整的块。

每次操作对每个整块直接标记,而由于不完整的块的元素比较少,暴力修改元素的值。

每次询问时返回元素的值加上其所在块的加法标记。每次操作的复杂度是O(n/m)+O(m)。

根据均值不等式,当m取√n时总复杂度最低,所以默认分块大小为√n,复杂度为O(√n)。

 

一. 分块的常用数组

int n,m; //总个数n,每块的大小m

int a[500019],tag[500019]; //原数组 和 标记数组(对于每一块)

int pos[500019]; //pos[i]=(i-1)/m+1,即记录i属于哪一块

还有一些在特定情况下使用的数组:

vector<int> v[519]; //记录每块的元素,并分别排序
//在每块中维护单调性,用lowerbound函数维护块的满足条件的值的个数

set<int> s[519]; //set记录每块的元素(已分别排序去重)

bool okk[500019]; //标记整个块内的元素是否全部满足某个条件

int b[N]; //b[i]用于数组离散化 或者 重新分块

int f[4021][4021]; //预处理出从第i块到第j块的总信息(比如区间众数)

int tag_add[500019],tag_mul[500019]; //多种标记...

 

二. 分块的常用函数

(1) void add(int l,int r,int x):用于区间的修改。在该函数中更新标记数组tag。

【add函数中一般包含三个步骤】a.处理l的边界块,暴力更新整块;

b.处理r的边界块,暴力更新整块;c.在中间的所有整块,打上标记。

(2) int query(int l,int r,int x):用于区间的查询。在该函数中整合所有修改值。

【query函数中一般包含三个步骤】a.处理l的边界块,暴力求值;

b.处理r的边界块,暴力求值;c.在中间的所有整块,整合所有标记,整体计算。

(3) void rebuild():重新分块。一般用于会添加元素的分块过程中。

【rebuild函数中一般包含三个步骤】a.记录a数组中的原状态到中转数组b中;

b.得到新的块大小,修改各个数组;c.将b数组的值完全赋给a数组。

(4) 数组的离散化+分块。一般用于数较大的时候,节约内存。

1.把a数组复制到b数组中; 2.b数组排序+去重;

3.将a数组中的数用【在b数组中的排名】表示出来;

4.完成离散化后,输出原值时,调用 b [ a [ i ] ]

sort(b+1,b+n+1); int n0=unique(b+1,b+n+1)-(b+1); //排序去重

for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n0+1,a[i])-b;
//↑↑寻找数值a[i]在离散化数组b中对应位置的下标(类似编号,排名)

(5) 其他函数。一般用于块间初始化问题,或者特殊的块间修改情况。

 

三. 分块练习题

      目录    【练习1】区间修改,单点查询 

【练习2】区间修改,查询比x小的个数

【练习3】区间修改,求区间内x的前驱

【练习4】区间修改,区间求和

【练习5】区间开方取整,区间求和

【练习6】单点插入,单点询问

【练习7】区间乘法,区间加法,单点查询

【练习8】区间询问,区间修改

【练习9】查询区间最小众数


【练习1】区间修改,单点查询

  • 每块标记tag,剩下的l、r两个边界块直接修改。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习1】区间修改,单点查询。*/

/*【分析】数列分块就是把数列中【每m个元素打包起来】,达到优化算法的目的。
如果把每m个元素分为一块,共有n/m块,区间加会涉及O(n/m)个整块,以及两侧两个不完整的块。
每次操作对每个整块直接标记,而由于不完整的块的元素比较少,暴力修改元素的值。
每次询问时返回元素的值加上其所在块的加法标记。每次操作的复杂度是O(n/m)+O(m)。
根据均值不等式,当m取√n时总复杂度最低,所以默认分块大小为√n,复杂度为O(√n)。*/

int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

int a[500019],tag[500019];

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(int l,int r,int x){ //l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x;
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x;
    for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x;
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n); m=sqrt(n);
    for(int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1,op,l,r,x;i<=n;i++){
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==0) adds(l,r,x);
        if(op==1) printf("%d\n",tag[pos[r]]+a[r]);
    } //↑↑op=1时,l、x输入但忽略
}

【练习2】区间修改,查询比x小的个数

  • vector数组记录每块的元素,排序维护块内的递增性。
  • 完整的块可以直接用lower_bound返回第一个大于等于x的位置。
  • 不完整的块直接暴力修改,但需要在每块内每次重新排序。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习2】区间修改,查询比x小的个数。*/

/*【分析】记录每块的元素,排序维护块内的递增性。
完整的块可以直接用lower_bound返回第一个大于等于x的位置。
不完整的块直接暴力修改,但需要在每块内每次重新排序。*/

int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

int a[500019],tag[500019]; 
//↑↑对整块使用tag标记,在询问的时候再计算tag的影响

vector<int> v[519]; //记录每块的元素,并分别排序

void changes(int num){
    v[num].clear(); //↓↓如果是最后一块,可能不完整
    for(int i=(num-1)*m+1;i<=min(num*m,n);i++)
        v[num].push_back(a[i]);
    sort(v[num].begin(),v[num].end());
}

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(int l,int r,int x){ //l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x;
    changes(pos[l]); //将
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值