之前提到过的块状链表和在树状数组中提到的块状求前缀和都是用了分块的思想。
分块:将已知数组分成若干块,再进行处理,这样每次只用处理至多块个数个大块和两个边界块
例一:求l-r有多少个数是3的倍数不是6的倍数1<l,r<1e7(此题比较简单,但在这里用上述技巧做)
解:设定每个块的大小为1e3,那么第一个块记录的是1-1e3有多少个满足条件的数,第二个块是1e3+1-2e3,那么最多需要加上1e4左右个大块,在遍历两个边界即可
例二:区间不同数的个数(莫队算法-优雅的暴力)
给一个数组,求l-r的不同数的个数
首先有一个暴力的做法,询问按左端点排序,因为我们可以从l-rO(1)的求出l-r+1,所以对于每一个左端点按序枚举所有右端点O(n2)就可以求出所有结果
莫队算法是在干什么呢?因为对于每个左端点我们都要枚举所有右端点,但有时询问比较少,大大小于n2,所以我们把左端点分成根号n块,对与每个块遍历右端点,这样就大大的降低了时间复杂度
代码:
#include<algorithm> #include<iostream> #include<cstdlib> #include<fstream> #include<cstring> #include<bitset> #include<cstdio> #include<time.h> #include<deque> #include<queue> #include<stack> #include<cmath> #include<map> #include<set> using namespace std; #define maxn 1010000 #define maxb 1100 int aa[maxn],cnt[maxn],belong[maxn]; int n,m,size,bnum,now,ans[maxn]; struct query{ int l,r,id; }q[maxn]; /* int cmp(query a,query b){//按块排序 return belong[a.l]==belong[b.l]?a.r<b.r:belong[a.l]<brlong[b.l]; } */ int cmp(query a,query b){//奇偶排序 return (belong[a.l]^belong[b.l])?belong[a.l]<belong[b.l]:((belong[a.l]&1)?a.r<b.r:a.r>b.r); } #define isdigit(x) ((x)>='0'&&(x)<='9') int read(){ int res=0; char c=getchar(); while(!isdigit(c))c=getchar(); while(isdigit(c))res=(res<<1)+(res<<3)+c-48,c=getchar(); return res; } void print(int x){ if(x/10)print(x/10); putchar(x%10+'0'); } int main(){ scanf("%d",&n); size=sqrt(n); bnum=ceil((double)n/size); for(int i=1;i<=bnum;++i){//分块 for(int j=(i-1)*size+1;j<=i*size;j++){ belong[j]=i; } } for(int i=1;i<=n;i++)aa[i]=read(); m=read(); for(int i=1;i<=m;i++){ q[i].l=read(),q[i].r=read(); q[i].id=i; } sort(q+1,q+m+1,cmp); int l=1,r=0; for(int i=1;i<=m;i++){ int ql=q[i].l,qr=q[i].r; while(l<ql)now-=!--cnt[aa[l++]]; while(l>ql)now+=!cnt[aa[--l]]++; while(r<qr)now+=!cnt[aa[++r]]++; while(r>qr)now-=!--cnt[aa[r--]]; ans[q[i].id]=now; } for(int i=1;i<=m;i++){ print(ans[i]),putchar('\n'); } return 0; }