bzoj3343 教主的魔法【分块入门】By cellur925
题意:维护一个数列,给出维护区间加法,询问区间内大于等于某个值的元素个数。
算法:分块。因为本题第二问显然可以用二分的思想,但是这貌似并不符合区间可加性,线段树好像就不好用了呢。所以本蒟蒻学习了分块。
这大概是本蒟蒻的第一题正式分块,思想是在hzwer学长的分块入门学的==。
什么是分块?我们维护数列(貌似树上也可以)信息时可以先采用分治的思想,把数列分成若干连续的块,维护信息可以统一在块上进行维护。假如有一个n元素的数列,根据均值不等式的数学知识(并不会证明),我们把每个块的大小设为根号n可以达到最右效果。但是显然有时根号n并不是整数==,没有关系,我们的数列上大部分都在整块上,只有小部分被遗弃的元素在不完整的块中。
我们进行区间操作时,把在整块中的元素进行整体操作,那些在不完整的块中的元素进行暴力操作。
总结来说,就是“大块维护,小段朴素”,这也是分块最基本的思想。
分块的复杂度(根号算法)虽然较其他数据结构可能略高了一些,却是打暴力的好方法,思维较简单,处理问题的范围更广,码量...可能略大(?)
回到本题的两个操作:
区间加法:对于一段区间,如果存在被整块覆盖的情况,直接在整块的加法标记上记录,不需在原序列上改动,查询时在加上加法标记;而在不完整的块中的元素,直接暴力修改。
查询大于等于X的元素个数:想一想如果数列是无序的,我们分的块就完全没用了。但是如果我们在预处理时以及修改时对每个整块进行排序,查询时调用lower_bound/手写二分查找,问题就能轻松得到解决。那些在不完整块中的元素,同理,暴力枚举统计。
综上,我们不难发现,使用分块算法需要或者只需要想的两个关键:
怎么维护整块?怎么维护不完整的块?
分块的更多代码实现细节:
(几乎每到分块题都要的)bl[i]记录i在第几个块
bl[i]*blo(blo=sqrt(n))可以得出当前位置(块的右边界),在此基础上变形
右边界:取min,bl[l]*blo,n(防止越界)
Code
1 // luogu-judger-enable-o2 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<vector> 6 7 using namespace std; 8 9 int n,Q,blo; 10 int val[1000090],bl[1000090],addtag[1000090]; 11 char opt[5]; 12 vector<int>cnt[5000]; 13 14 void update(int x) 15 { 16 cnt[x].clear(); 17 for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++) 18 cnt[x].push_back(val[i]); 19 sort(cnt[x].begin(),cnt[x].end()); 20 } 21 22 void add(int l,int r,int p) 23 { 24 if(l==r) val[l]+=p; 25 else 26 { 27 for(int i=l;i<=min(bl[l]*blo,r);i++) 28 val[i]+=p; 29 update(bl[l]); 30 if(bl[l]!=bl[r]) 31 { 32 for(int i=(bl[r]-1)*blo+1;i<=r;i++) 33 val[i]+=p; 34 update(bl[r]); 35 } 36 for(int i=bl[l]+1;i<=bl[r]-1;i++) 37 addtag[i]+=p; 38 } 39 } 40 41 int query(int l,int r,int p) 42 { 43 int ans=0; 44 for(int i=l;i<=min(bl[l]*blo,r);i++) 45 if(val[i]+addtag[bl[l]]>=p) ans++; 46 if(bl[l]!=bl[r]) 47 { 48 for(int i=(bl[r]-1)*blo+1;i<=r;i++) 49 if(val[i]+addtag[bl[r]]>=p) ans++; 50 } 51 for(int i=bl[l]+1;i<=bl[r]-1;i++) 52 { 53 int tmp=p-addtag[i]; 54 int pos=lower_bound(cnt[i].begin(),cnt[i].end(),tmp)-cnt[i].begin(); 55 ans+=blo-pos; 56 // ans+=find(i,tmp); 57 } 58 return ans; 59 } 60 61 int main() 62 { 63 scanf("%d%d",&n,&Q); 64 for(int i=1;i<=n;i++) scanf("%d",&val[i]); 65 blo=sqrt(n); 66 for(int i=1;i<=n;i++) 67 { 68 bl[i]=(i-1)/blo+1; 69 cnt[bl[i]].push_back(val[i]); 70 } 71 for(int i=1;i<=bl[n];i++) 72 sort(cnt[i].begin(),cnt[i].end()); 73 while(Q--) 74 { 75 scanf("%s",opt+1); 76 int x=0,y=0,z=0; 77 scanf("%d%d%d",&x,&y,&z); 78 if(opt[1]=='M') 79 add(x,y,z); 80 else if(opt[1]=='A') 81 printf("%d\n",query(x,y,z)); 82 } 83 return 0; 84 }
1 // luogu-judger-enable-o2 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<vector> 6 7 using namespace std; 8 9 int n,Q,blo; 10 int val[1000090],bl[1000090],addtag[1000090]; 11 char opt[5]; 12 vector<int>cnt[5000]; 13 14 void update(int x)//针对散块 15 {//清空,重新读入 16 cnt[x].clear(); 17 for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++) 18 cnt[x].push_back(val[i]); 19 sort(cnt[x].begin(),cnt[x].end()); 20 } 21 22 void add(int l,int r,int p) 23 { 24 if(l==r) val[l]+=p; 25 else 26 { 27 for(int i=l;i<=min(bl[l]*blo,r);i++) 28 val[i]+=p;//左散块 29 update(bl[l]);//重新排序 30 if(bl[l]!=bl[r]) 31 {//右散块 32 for(int i=(bl[r]-1)*blo+1;i<=r;i++) 33 val[i]+=p; 34 update(bl[r]); 35 } 36 //整块 37 for(int i=bl[l]+1;i<=bl[r]-1;i++) 38 addtag[i]+=p; 39 } 40 } 41 42 int query(int l,int r,int p) 43 { 44 int ans=0; 45 //左半散块 46 for(int i=l;i<=min(bl[l]*blo,r);i++) 47 if(val[i]+addtag[bl[l]]>=p) ans++; 48 if(bl[l]!=bl[r])// ! 49 {//右半散块 50 for(int i=(bl[r]-1)*blo+1;i<=r;i++) 51 if(val[i]+addtag[bl[r]]>=p) ans++; 52 } 53 for(int i=bl[l]+1;i<=bl[r]-1;i++)//整块 54 { 55 int tmp=p-addtag[i];//求>=p,则先把加法标记减去 56 int pos=lower_bound(cnt[i].begin(),cnt[i].end(),tmp)-cnt[i].begin(); 57 ans+=blo-pos;//注意这句 精髓 58 // ans+=find(i,tmp); 59 } 60 return ans; 61 } 62 63 int main() 64 { 65 scanf("%d%d",&n,&Q); 66 for(int i=1;i<=n;i++) scanf("%d",&val[i]); 67 blo=sqrt(n); 68 for(int i=1;i<=n;i++) 69 { 70 bl[i]=(i-1)/blo+1; 71 cnt[bl[i]].push_back(val[i]);//记录每个块中元素 72 } 73 //对每个块中元素进行排序 74 for(int i=1;i<=bl[n];i++) 75 sort(cnt[i].begin(),cnt[i].end()); 76 while(Q--) 77 { 78 scanf("%s",opt+1); 79 int x=0,y=0,z=0; 80 scanf("%d%d%d",&x,&y,&z); 81 if(opt[1]=='M') 82 add(x,y,z); 83 else if(opt[1]=='A') 84 printf("%d\n",query(x,y,z)); 85 } 86 return 0; 87 }
然鹅不吸氧会T掉一个点,可能是我lowerbound常数的锅锅?讨论里有人说要用longlong结果我用了longlong只会带来无端的CE/WA/RE。
Just be cautious.