首页 > 技术文章 > 分块学习笔记

ifmyt 2018-08-19 15:26 原文

在我不知道分块以前,我一直以为分块是一个非常牛逼的东西。在我多次学习并且处于懵逼状态的时候,我一直以为这辈子我不会分块了。直到一天我学会了他。

      (ps:一个小建议,学习新知识要在上午哦)

下面我就把刚刚学会的分块做了一下总结。

主要思想:

分块是一个很暴力的算法,跟普通的枚举暴力差不了多少。

对于一个长度为n的序列,我们可以讲其中的元素分为M个连续的子序列,每块的长度自然就为N/M。我们在更新一段区间[l,r]是,可以先更新l到l所在块的右端点和r所在块的左端点到r。即下图中红色的区域,每块中最多有N/M个元素,所以这一操作的复杂度的为N/M。

然后我们在成段更新刚才更新的块中间的那些块(即上图中红色区域中间的那些块),这些块最多为N块,所以这一操作的复杂度为M。

总操作的复杂度即为M+N/M,根据均值不等式可知,M=sqrt(n)时复杂度最新。

 

下面的就是模板(原谅我草率的写完,因为这东西是真的没有好写的)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<cmath>
 7 using namespace std;
 8 const int N = 100000;
 9 int gro[N],blo,n,v[N],tag[N];
10 void add(int a,int b,int c)
11 {
12     for(int i=a;i<=min(b,gro[a]*blo);i++)//这是确定右断点,可能是同一个块,可能是不同块 
13         v[i]+=c;
14     if(gro[a]!=gro[b])
15         for(int i=(gro[b]-1)*blo+1;i<=b;i++)//左端点 
16             v[i]+=c;
17     for(int i=gro[a]+1;i<=gro[b]-1;i++)
18         tag[i]+=c;//标记 
19 }
20 int ask(int a,int b,int c)
21 {
22     int ans=0;
23     for(int i=a;i<=b;i++)
24         if(v[i]+tag[gro[i]]<c)ans++;
25     return ans;
26 }//暴力询问即可 
27 int main()
28 {
29     scanf("%d",&n);
30     for(int i=1;i<=n;i++)scanf("%d",&v[i]);
31     blo=sqrt(n);
32     for(int i=1;i<=n;i++)gro[i]=(i-1)/blo+1;//为什么是i-1呢?自己手玩一下就知道了 
33     for(int i=1;i<=n;i++)
34     {
35         int opt,l,r,c;
36         scanf("%d%d%d%d",&opt,&l,&r,&c);
37         if(opt==0)add(l,r,c);
38         if(opt==1)printf("%d\n",ask(l,r,pow(c,2)));
39     }
40 }

例题:LOJ数列分块入门一

   LOJ数列分块入门二

推荐阅读