看了hzwer的博客,受益匪浅,于是来分享一下自己的想法。

首先,分块是用来干啥的呢?简单点说,就是一个处理数据结构的高级暴力。 如果我们想要修改一个序列并查询,一个一个的模拟显然是太慢了的,但有些东西又不能用线段树来维护,那么怎么办呢?这时候就分块就派上用场了。

 

先沿用几个黄学长使用的概念:

  • 整块:在一个区间操作时,完整包含于区间的块
  •  不完整的块:在一个区间操作时,只有部分包含于区间的块,即区间左右端点所在的两个块

 

下面讲一下分块方法:

  1. 将一个序列分成m块,每个块有(n/m)个元素。
  2. 在修改区间时对于询问左端点所在的区间和右端点所在的区间暴力修改元素。
  3. 对于左端点和右端点之间的区间直接打上区间修改标记。
  4. 查询也是对于不在一个整块的区间暴力修改,在整块中的区间通过区间标记查询。

因为被分成了m块,所以时间复杂度约为O(n/m+m),根据均值不等式,m取sqrt(n)时时间复杂度取得最小值(当然根据题目不同也可以改变分块的大小)。

 

然后将块分开了之后,就要记录每个块的信息了。那么按照每块sqrt(n)个元素,会分成这样:

  • 每个块内有sqrt(n)个元素(用block代替)
  • 每个块i的所涵盖的区间的左端点下标为(i-1)*block+1,右端点下标为i*block(左右都是闭区间)。
  • 序列最后面可能有一些零散的元素,属于第block+1块,没有被分入整块中。

通过这些信息,我们就可以确定一个区间所属于哪些块,以及一个块所涵盖的区间。

 

分块的代码比较灵活,可以记录各种各样的东西,下面以黄学长的分块入门1为例:

  题目大意就是给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,单点查值。

  

对于区间加法,我们可以将修改的区间中完整的区间打上区间标记,对于在不完整的块上的元素直接暴力修改。

查询则直接输出该元素的值加上它所在的区间的标记值。

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=50000+5;
 4 
 5 int n, m;
 6 int w[N];//w[i]记录下标为i的元素的值
 7 int b[N];//b[i]记录下标为i的元素属于第b[i]个块
 8 int block;//每个块有block个元素
 9 int lazy[N];//记录对整块的操作
10 
11 int gi(){//读入优化
12     int ans = 0 , f = 1; char i = getchar();
13     while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
14     while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
15     return ans * f;
16 }
17 
18 void add(int x,int y,int z){
19     for(int i=x;i<=min(b[x]*block,y);i++) w[i] += z;//修改x所在的不完整块
20     for(int i=b[x]+1;i<=b[y]-1;i++) lazy[i] += z;//对整块进行修改
21     if(b[x] != b[y])//因为如果x和y在同一个块内,则在修改x所在的不完整块的时候就已经修改过了
22         for(int i=(b[y]-1)*block+1;i<=y;i++) w[i] += z;//修改y所在的不完整块
23 }
24 
25 int main(){
26     int flag, x, y, val; n = gi();
27     block = sqrt(n);
28     for(int i=1;i<=n;i++) w[i] = gi();
29     for(int i=1;i<=n;i++) b[i] = (i-1)/block+1;//处理每个元素所在的块
30     for(int i=1;i<=n;i++){
31     flag = gi(); x = gi(); y = gi(); val = gi();
32     if(flag == 0) add(x,y,val);
33     else printf("%d\n",w[y]+lazy[b[y]]);
34     }
35     return 0;
36 }

 

 

下面分析一下其他的分块入门题:

  分块入门2

    给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数。

 

既然要统计小于某个值的元素个数,那么同样可以直接暴力统计出在不完整的块上的元素小于 x 的个数。而对于整块中的元素,可以直接另外开一个数组对整块排序,并对这个块二分查找。还有要注意的细节是不能直接对原数组进行排序,而要另外开一个数组来排序,在二分查找的时候要用这个排序后的数组进行查找。因为如果直接对原数组进行排序的话,就无法保存原位置的值,会导致查询不完整区块时出现问题。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=50000+5;
 4 
 5 int w[N], b[N], v[N];
 6 int block, n, lazy[N];
 7 
 8 void updata(int blo){
 9     int l = (blo-1)*block+1 , r = min(blo*block,n);
10     for(int i=l;i<=r;i++) v[i] = w[i];
11     sort(v+l , v+r+1);
12 }
13 
14 void add(int x,int y,int val){
15     for(int i=x;i<=min(y,b[x]*block);i++) w[i] += val;
16     for(int i=b[x]+1;i<=b[y]-1;i++) lazy[i] += val;
17     for(int i=(b[y]-1)*block+1;i<=y && b[x]!=b[y];i++) w[i] += val;
18     updata(b[x]); updata(b[y]);
19 }
20 
21 int query(int x,int y,int z){
22     int res = 0 , whole = 0 , small = 0;
23     for(int i=x;i<=min(y,b[x]*block);i++)
24     if(w[i]+lazy[b[x]] < z) res++ , small++;
25     for(int i=b[x]+1;i<=b[y]-1;i++){
26     int l = (i-1)*block+1 , r = i*block , pos = l-1 , st = l-1;
27     while(l <= r){
28         int mid = (l+r >> 1);
29         if(v[mid]+lazy[i] < z) l = mid+1 , pos = mid;
30         else r = mid-1;
31     }
32     res += pos-st; whole += pos-st;
33     }
34     for(int i=(b[y]-1)*block+1;i<=y && b[x]!=b[y];i++)
35     if(w[i]+lazy[b[y]] < z) res++ , small++;
36     //printf("ans=%d small=%d whole=%d\n",res,small,whole);
37     printf("%d\n",res);
38 }
39 
40 int main(){
41     //freopen("a1.in","r",stdin);
42     //freopen("ans.out","w",stdout);
43     ios::sync_with_stdio(false);
44     int x, y, z, flag;
45     cin >> n; block = sqrt(n);
46     for(int i=1;i<=n;i++) cin >> w[i];
47     for(int i=1;i<=n;i++) b[i] = (i-1)/block+1;
48     memcpy(v,w,sizeof(v));
49     for(int i=1;i<=block;i++)
50     sort(v+(i-1)*block+1,v+i*block+1);
51     for(int i=1;i<=n;i++){
52     cin >> flag >> x >> y >> z;
53     if(flag == 0) add(x,y,z);
54     else query(x,y,z*z);
55     }
56     return 0;
57 }
View Code

相关文章:

  • 2022-12-23
  • 2021-06-02
  • 2021-04-01
  • 2021-12-11
  • 2021-07-03
  • 2021-05-23
  • 2022-12-23
  • 2021-08-06
猜你喜欢
  • 2021-12-02
  • 2022-02-10
  • 2021-06-12
  • 2021-10-10
  • 2021-09-11
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案