---恢复内容开始---
v1.0,征求大佬的修改意见
众所周知,欣隆妹妹是个毒瘤。
经常出毒瘤数据结构题。
出的最多的就是分块。
那么,窝们今天就来学习优(暴)秀(力)数据结构--分块
可爱的由乃。
QAQ
part-one 什么是分块?
分块,顾名思义,就是把序列分成相等的若干个块,对于每一个块,维护信息,然后再合并。
part-two 分块的原理,实现?
现在,我们有一个问题,对一个序列,区间加法,求区间每一个数的和(假设我们不会一种东西叫线段树)
暴力做法:
暴力修改,暴力查询,显然T飞,时间复杂度O(n²)。
考虑优化暴力:
把序列分成ω个块,每一个块都维护一个TAG。
l,r区间分为整块部分和零散块部分。
对于整块的部分,我们直接对块的TAG加上x。
对于零散的部分,我们暴力加。
查询也是一样。
对于整块的部分,就是块内原来的和+(n/ω)*TAG>
对于零散的部分,直接暴力查询。
时间复杂度。
整块最多ω个,所以时间复杂度O(ω)。
零散部分最多(n/ω)个,时间复杂度O(n/ω)。
显然ω=sqrt(n)是最优。
所以分块时间复杂度m*sqrt(n).
分块大概实现:
记录bel数组,代表一个点属于哪一个块。
记录lft,rgt数组,代表每一个块的左端点和右端点
然后对与每一个块统计信息。
对于每一次查询
若两个端点在同一个块中,则暴力查询。
否则先查询整块信息,再暴力查询零散部分
对于每一个修改。
整块信息直接打TAG,零散部分暴力修改。
part three-例题(因不同题写题时间可能差异较大,码风可能不同)
1.弹飞绵羊(luogu3203)
题目大意:Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系数ki,当绵羊达到第i个装置时,它会往后弹ki步,达到第i+ki个装置,若不存在第i+ki个装置,则绵羊被弹飞。绵羊想知道当它从第i个装置起步时,被弹几次后会被弹飞。
基础题,对装置分块,记录每一个点跳几次能跳出当前所在块,跳出后跳到几号节点。
代码:
#include <iostream>#include <stdio.h> #include <string> #include <math.h> using namespace std; const int maxn=200005,maxm=505; int n,m,Q,num[maxn],bel[maxn],sum[maxn],outt[maxn]; struct block { int l,r; } a[maxn]; inline int read() { int x=0; char ch=getchar(); while (ch<‘0‘||ch>‘9‘) ch=getchar(); while (ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar(); return x; } void doit(int l,int r) { for (int i=r; i>=l; i--) if (i+num[i]>a[bel[i]].r) sum[i]=1,outt[i]=i+num[i]; else sum[i]=sum[i+num[i]]+1,outt[i]=outt[i+num[i]]; } int main() { n=read(); m=sqrt(n); if (m*m<n) m++; int s=0; for (int i=1; i<=n; i++) num[i]=read(); for (int i=1; i<=n; i+=m) a[++s].l=i,a[s].r=i+m-1; if (s<m) a[++s].l=s*m+1,a[s].r=n; s=1; for (int i=1; i<=n; i++) { if (i>a[s].r) s++; bel[i]=s; } doit(1,n); Q=read(); while (Q--) { int x=read(),y=read()+1; if (x==1) { int ans=sum[y],x=outt[y]; for (int i=bel[y]; i<=m&&x<=n; i++) ans+=sum[x],x=outt[x]; printf("%d\n",ans); } else { int z=read(); num[y]=z; doit(a[bel[y]].l,a[bel[y]].r); } } return 0; }
2.lucky array(CF121E)
其实这题更简单,本该放在前面。
题目大意:区间加法,区间幸运数个数。
因为值域很小,我们发现在值域中的幸运数个数为30,所以我们先打个表,然后分块。
先统计每一个块内每一个数的出现次数,然后对于加法,我们还是打TAG,只不过在查询的时候要查询=(幸运数字-TAG)的个数
然后就好了。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define Rint register int #define Temp template<typename T> #define update(x,y,z) num[x][y]--,num[x][y+=z]++ using namespace std; Temp inline void read(T &x) { x=0;T w=1,ch=getchar(); while(!isdigit(ch)&&ch!=‘-‘) ch=getchar(); if(ch==‘-‘) w=-1,ch=getchar(); while(isdigit(ch)) x=x*10+ch-‘0‘,ch=getchar(); x*=w; } const int t[30]={4,7,44,47,74,77,444,447,474,477,744,747,774,777,4444,4447,4474,4477 ,4744,4747,4774,4777,7444,7447,7474,7477,7744,7747,7774,7777}; const int m=317*5; const int maxn=1e5+10; int n,q,cnt; int a[maxn]; int num[105][maxn],id[maxn],low[maxn],high[maxn],add[maxn]; int vis[10010]; int main() { read(n);read(q); for (int i=1;i<=n;++i) { read(a[i]); id[i]=i/m+1; num[id[i]][a[i]]++; } cnt=id[n]; for (int i=1;i<=cnt;++i) { low[i]=(i-1)*m; high[i]=i*m-1; } low[1]=1;high[cnt]=n; for (int i=0;i<30;++i) vis[t[i]]=1; while(q--) { char kind[10]; int l,r,d; scanf("%s%d%d",kind,&l,&r); int lx=id[l],rx=id[r]; if(*kind==‘a‘) { scanf("%d",&d); if(lx==rx) { for (int i=l;i<=r;++i) update(lx,a[i],d); } else { for (int i=l;i<=high[lx];++i) update(lx,a[i],d); for (int i=lx+1;i<rx;++i) add[i]+=d; for (int i=low[rx];i<=r;++i) update(rx,a[i],d); } } else { int ans=0; if(lx==rx) { for (int i=l;i<=r;++i) ans+=vis[a[i]+add[lx]]; } else { for (int i=l;i<=high[lx];++i) ans+=vis[a[i]+add[lx]]; for (int i=lx+1;i<rx;++i) { for (int j=0;j<30;++j) if(add[i]<=t[j]) ans+=num[i][t[j]-add[i]]; } for (int i=low[rx];i<=r;++i) ans+=vis[a[i]+add[rx]]; } printf("%d\n",ans); } } return 0; }
part four-Ynoi(不提供代码,不然没啥意思)
1.luogu5397 天降之物(第四分块)
题目大意:将所有x变成y,求最小的|i-j|使得a[i]==x && a[j]==y。
定义size[x]为x出现次数.
by lxl(写了一遍题解发现自己写得太垃圾了)
2. 五彩斑斓的世界。(第二分块)
题目大意:把区间>x的数减去x,求区间l,r内x的出现次数,值域1e5
解法:
提示了值域1e5,时间复杂度肯定与值域有关。
这题明显有一个性质,就是所有数的最大值总是单调不增的。
考虑利用这个性质
然后,他是要把l,r内>x的数-x,可一转化为把所有数-x,然后把<0的数+x
当最大值>=x*2时,把(1,x)合并到(x+1,x*2)
否则把(x+1,v)合并到(1,v-x)
这个用并查集维护即可。
所以神仙多多指教啊
---恢复内容结束---
原文地址:https://www.cnblogs.com/zjjandrew/p/11302466.html