在我不知道分块以前,我一直以为分块是一个非常牛逼的东西。在我多次学习并且处于懵逼状态的时候,我一直以为这辈子我不会分块了。直到一天我学会了他。
(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 ",ask(l,r,pow(c,2))); 39 } 40 }
例题:LOJ数列分块入门一