关于分块的个人理解(一)
上周开始讲分块,然而和我想的不一样,大家都听得很懵13 --> 以至于我对于自己对于分块的理解产生了怀疑。
结果自然是写一篇blog自我检验一下了啊。
那么切入正题。
分块
简意
将一段暴力很费事的区间拆分成数个小区间分开求解,并采取预处理的方式降低复杂度。
这种算法真的是很好的啊。
尤其是对于我们这种懒惰oier来说,简直就是福音啊。
实现方法
将一段长序列分成相同的块大小,事先将整块的数据完成整理。然后进行整散拆分,整块直接使用,散块暴力。
那么这又有什么无法理解的地方呢?我真的很不理解QAQ
那么接下来我们采取例题的方式来更具象的理解一下吧。
基础. luogu P3870
题目描述:
现有N(2 ≤ N ≤ 100000)盏灯排成一排,从左到右依次编号为:1,2,......,N。
然后依次执行M(1 ≤ M ≤ 100000)项操作,操作分为两种:
第一种操作指定一个区间[a, b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开)。
第二种操作是指定一个区间[a, b],要求你输出这个区间内有多少盏灯是打开的。灯在初始时都是关着的。
输入:
第一行有两个整数N和M,分别表示灯的数目和操作的数目。
接下来有M行,每行有三个整数,依次为:c, a, b。其中c表示操作的种类:
当c的值为0时,表示是第一种操作。
当c的值为1时表示是第二种操作。
a和b则分别表示了操作区间的左右边界(1 ≤ a ≤ b ≤ N)。
输出:
每当遇到第二种操作时,输出一行,包含一个整数:此时在查询的区间中打开的灯的数目。
样例输入:
4 5 0 1 2 0 2 4 1 2 3 0 2 4 1 1 4样例输出:
1 2
解法:
这种东西真的很显然了啊,我都说了是分块的裸题了啊QAQ
我们在修改的时候对于散块直接暴力修改,整块把用来标记的数组异或一下 1 就行了,修改次数为偶数时灯自然就不变了啊。
但这样的话不好查整块,所以需要维护一个ans[i],用于保存块内目前开着的灯的数量。
散块修改的时候好维护,而整块修改的时候把用块的大小减去ans维护就行了。
那么查询的时候自然也很显然了啊,散块暴力,整块ans。
代码奉上:
风格较丑,请见谅哈。
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> #include<queue> #define rint register int #define maxn 200010 using namespace std; inline int read() { int s=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){ch=getchar();if(ch=='-')f=-1;} while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar(); return s*f; } int n,m,blo,o; int bl[maxn],v[maxn],t[maxn],atag[maxn],sum[maxn]; int L[maxn],R[maxn]; inline void change(int l,int r) { if(bl[l]==bl[r]) { for(rint i=l;i<=r;++i) { if(v[i]) { v[i]=0; --sum[bl[l]]; } else { v[i]=1; ++sum[bl[l]]; } } return; } for(rint i=l;i<=R[bl[l]];++i) { if(v[i]) { v[i]=0; --sum[bl[l]]; } else { v[i]=1; ++sum[bl[l]]; } } for(rint i=L[bl[r]];i<=r;++i) { if(v[i]) { v[i]=0; --sum[bl[r]]; } else { v[i]=1; ++sum[bl[r]]; } } for(rint i=bl[l]+1;i<=bl[r]-1;++i) atag[i]=1-atag[i]; } inline int query(int l,int r) { int ans=0; if(bl[l]==bl[r]) { for(rint i=l;i<=r;++i) if(v[i]^atag[bl[l]]) ++ans; return ans; } for(int i=l;i<=R[bl[l]];++i) if(v[i]^atag[bl[l]]) ++ans; for(int i=L[bl[r]];i<=r;++i) if(v[i]^atag[bl[r]]) ++ans; for(int i=bl[l]+1;i<=bl[r]-1;++i) { if(atag[i]) ans+=(R[i]-L[i]+1)-sum[i]; else ans+=sum[i]; } return ans; } int main() { n=read(); m=read(); blo=sqrt(n); n%blo?(o=n/blo+1):(o=n/blo); for(rint i=1;i<=n;++i) { v[i]=0; bl[i]=(i-1)/blo+1; if(v[i]) ++sum[bl[i]]; } for(rint i=1;i<=o;++i) { L[i]=(i-1)*blo+1; R[i]=i*blo; } while(m--) { int p=read(); int l=read(); int r=read(); if(p==0) change(l,r); if(p==1) cout<<query(l,r)<<endl; } return 0; }
如此这题就完了耶~
此处有彩蛋哦~
作为一个懒 的打题的人怎么能放过双倍经验呢??????
P2846 [USACO08NOV]光开关Light Switching
祝大家RP++!!