分块
分块#
超级暴力,超好想,超好打,超好用
无脑首选
下面的例题较简单的就不放代码
#6277. 数列分块入门 1#
区间加法单点求值
边角块暴力,区间打标记
时间复杂度
#6278. 数列分块入门 2#
区间加法,查询区间内小于某一个数的个数
对于每一个块我们维护块内有序
修改时边角块暴力修改,然后暴力重排,区间直接打标记
查询时边角块暴力,区间直接二分 (因为每个块都是有序的)
时间复杂度
#define rep(i,l,r) for(register int i=l;i<=r;++i)
int n,size,a[N],bl[N],tag[N];
vector <int> p[505];
inline void rebuild(int x){
p[x].clear();
for(int i=(x-1)*size+1;i<=min(x*size,n);i++)
p[x].push_back(a[i]);
sort(p[x].begin(),p[x].end());
}
inline void add(int l,int r,int val){
for(int i=l;i<=min(bl[l]*size,r);i++) a[i]+=val;
rebuild(bl[l]);
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*size+1;i<=r;i++) a[i]+=val;
rebuild(bl[r]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++) tag[i]+=val;
}
inline int work(int l,int r,int c){
int ans=0;
for(int i=l;i<=min(bl[l]*size,r);i++)
if(a[i]+tag[bl[l]]<c) ans++;
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*size+1;i<=r;i++)
if(a[i]+tag[bl[r]]<c) ans++;
for(int i=bl[l]+1;i<=bl[r]-1;i++) {
int x=c-tag[i];
ans+=lower_bound(p[i].begin(),p[i].end(),x)-p[i].begin();
}
return ans;
}
int main(){
n=read();size=sqrt(n);
rep(i,1,n) a[i]=read();
rep(i,1,n){bl[i]=(i-1)/size+1;p[bl[i]].push_back(a[i]);}
for(int i=1;i<=bl[n];i++) sort(p[i].begin(),p[i].end());
rep(i,1,n){
int f=read(),x=read(),y=read(),val=read();
if(!f) add(x,y,val);
else cout<<work(x,y,val*val)<<endl;
}
return 0;
}
#6279. 数列分块入门 3#
区间加,查询某个数的前驱/后继
对于每一个块我们维护块内有序
修改时边角块暴力修改,然后暴力重排,区间直接打标记
查询时边角块暴力,区间直接二分 (因为每个块都是有序的)
时间复杂度
#6280. 数列分块入门 4#
区间加,区间求和
区间打标记即可
时间复杂度
#6281. 数列分块入门 5#
区间开方,区间查询
这东西暴力搞就行,能过
#6282. 数列分块入门 6#
单点插值,单点查询
显然我们先分块,然后暴力插,每 次插入就重构
时间复杂度
int n,cnt,t,sz,mx;
int a[N],bl[N];
vector <int> p[N];
inline void build(int op){
sz=sqrt(t);
int tot=0;
if(op){
for(int i=1;i<=mx;++i){
for(auto x:p[i])
a[++tot]=x;
p[i].clear();
}
}
for(int i=1;i<=t;++i){
bl[i]=(i-1)/sz+1;
p[bl[i]].push_back(a[i]);
}
mx=bl[t];
//if(op) db1();
}
inline void insert(int b,int x,int k){
if(x==p[b].size()){
p[b].push_back(x);
return;
}
p[b].insert(p[b].begin()+x,k);
if(p[b].size()>=5*sz) build(1);
}
inline void ins(int x,int y){
for(int i=1;i<=mx;++i){
if(p[i].size()>=x){
insert(i,x-1,y);
return;
}
else x-=p[i].size();
}
}
inline int qry(int b,int x){
return p[b][x-1];
}
inline int query(int x){
for(int i=1;i<=mx;++i){
if((int)p[i].size()>=x){
return qry(i,x);
}
else x-=p[i].size();
}
}
signed main(){
n=read();t=n;
for(int i=1;i<=n;++i) a[i]=read();
build(0);
for(int i=1;i<=n;++i){
int op=read(),l=read(),r=read();read();
if(op==0) ins(l,r),++cnt,++t;
else printf("%d\n",query(r));
}
}
#6283. 数列分块入门 7#
区间乘法,区间加法,单点查询
显然先乘后加
#6284. 数列分块入门 8#
区间询问等于一个数 的元素有多少个,并将这个区间的所有元素改为
显然整块打标记,边角块暴力重构
#6285. 数列分块入门 9#
同下蒲公英
P4168 [Violet]蒲公英#
个数, 次操作
每次查询区间 的区间众数是几
如果有多个就输出最小的那一个
强制在线
思路:
看到强制在线,看到维护众数,看到 ,想到分块
考虑什么样的数可以是区间内的众数:
-
边角块的每一个数
-
整块的众数
我们可以考虑将在询问时上述这些数加入备选答案中
若块取 ,那么最多会有 个数,应此每个数的出现次数都必须 算出
首先离散化
由于这题不卡空间,我们记录 表示前 个块内 出现的次数, 表示块 的众数是哪个
那么显然 可以在 的时间内预处理
可以为且只能为 或是块 内中的任意数
我们可以暴力统计哪个数出现次数最多
显然这样更新每个 都是 的,总共是 的
然后在询问时我们就可以利用 来 的求出连续几个整块内每个数出现的次数,总共的复杂度是 的
这样下来总复杂度为 可以接受
具体实现的细节看代码
code:
int n,m,sz,ct;
int v[N],a[N],bl[N],L[N],R[N],cnt[S][N];
int cb[N],num[S][S];
inline int query(int x,int y){
int l=bl[x],r=bl[y];
vector <int> p;
if(l==r){
int mx=0,id=0;
for(int i=x;i<=y;++i){
++cb[v[i]];
if(cb[v[i]]>mx||(cb[v[i]]==mx&&v[i]<id)) mx=cb[v[i]],id=v[i];
}
for(int i=x;i<=y;++i) cb[v[i]]=0;
return id;
}
for(int i=x;i<=R[l];++i){
if(!cb[v[i]])
p.push_back(v[i]);
++cb[v[i]];
}
for(int i=L[r];i<=y;++i){
if(!cb[v[i]])
p.push_back(v[i]);
++cb[v[i]];
}
if(l==r-1){
int mx=0,id=0;
for(auto q:p){
if(cb[q]>mx||(cb[q]==mx&&q<id)) mx=cb[q],id=q;
cb[q]=0;
}
return id;
}
int id=num[l+1][r-1],mx=cb[id]+cnt[r-1][id]-cnt[l][id];
cb[id]=0;
for(auto q:p){
if(q==id) continue;
int now=cnt[r-1][q]-cnt[l][q]+cb[q];
if(now>mx||(now==mx&&q<id)) mx=now,id=q;
cb[q]=0;
}
return id;
}
signed main(){
n=read(),m=read();
sz=sqrt(n);
for(int i=1;i<=n;++i){
v[i]=read();a[i]=v[i];
bl[i]=(i-1)/sz+1;
if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
}
R[bl[n]]=n;
sort(a+1,a+n+1);
ct=1;
for(int i=2;i<=n;++i)
if(a[i]!=a[i-1]) a[++ct]=a[i];
for(int i=1;i<=n;++i)
v[i]=lower_bound(a+1,a+ct+1,v[i])-a;
for(int i=1;i<=bl[n];++i){
for(int j=0;j<=ct;++j) cnt[i][j]=cnt[i-1][j];
for(int j=L[i];j<=R[i];++j)
++cnt[i][v[j]];
}
for(int i=1;i<=bl[n];++i)
for(int j=1;j<=i;++j){
int t=num[j][i-1],mx=(t!=0)*(cnt[i][t]-cnt[j-1][t]);
for(int k=L[i];k<=R[i];++k){
if(cnt[i][v[k]]-cnt[j-1][v[k]]>mx||(cnt[i][v[k]]-cnt[j-1][v[k]]==mx&&v[k]<t))
mx=cnt[i][v[k]]-cnt[j-1][v[k]],t=v[k];
}
num[j][i]=t;
}
int x=0;
while(m--){
int l=read(),r=read();
l=(l+x-1)%n+1;
r=(r+x-1)%n+1;
if(l>r) swap(l,r);
x=a[query(l,r)];
printf("%d\n",x);
}
}
P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III#
给你一个长为 的序列 , 次询问,每次查询一个区间的众数的出现次数,强制在线。
思路:
这题卡了空间,我们需要另辟蹊径
考虑每个区间 的众数出现次数为
所以我们只需要预处理每个块 到块 的众数出现次数,然后暴力搞边角块就行了
考虑边角块如何暴力
我们搞一个 数组 用于存储数 所有出现的位置,搞一个 表示 在 出现的位置是哪
对于左边的块,我们
int ps=pos[i];
while(ps+ans<v[a[i]].size()&&v[a[i]][ps+ans]<=y) ++ans;
对于右边的块,我们
int ps=pos[i];
while(ps-ans>=0&&v[a[i]][ps-ans]>=x) ++ans;
就是看当前的答案够不够优,如果不够就不断增加
显然这样每次询问 最多增加 次,可以接受
总时间复杂度是 的,空间复杂度是 的,可以接受
code:
int n,m,sz;
int a[N],b[N],pos[N];
int bl[N],L[S],R[S],sum[N],p[S][S];
vector <int> v[N];
inline void build(){
for(int i=1;i<=bl[n];++i){
for(int j=i;j<=bl[n];++j){
p[i][j]=p[i][j-1];
for(int k=L[j];k<=R[j];++k){
++sum[a[k]];
p[i][j]=max(p[i][j],sum[a[k]]);
}
}
memset(sum,0,sizeof(sum));
}
for(int i=1;i<=n;++i) v[a[i]].push_back(i),pos[i]=v[a[i]].size()-1;
}
inline int query(int x,int y){
int l=bl[x],r=bl[y];
int ans=0;
if(l==r){
for(int i=x;i<=y;++i){
++sum[a[i]];
ans=max(ans,sum[a[i]]);
}
for(int i=x;i<=y;++i)
sum[a[i]]=0;
return ans;
}
ans=p[l+1][r-1];
for(int i=x;i<=R[l];++i){
int ps=pos[i];
while(ps+ans<v[a[i]].size()&&v[a[i]][ps+ans]<=y) ++ans;
}
for(int i=L[r];i<=y;++i){
int ps=pos[i];
while(ps-ans>=0&&v[a[i]][ps-ans]>=x) ++ans;
}
return ans;
}
signed main(){
in>>n>>m;
sz=sqrt(n);
for(int i=1;i<=n;++i){
in>>a[i];b[i]=a[i];
bl[i]=(i-1)/sz+1;
if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
}
R[bl[n]]=n;
stable_sort(b+1,b+n+1);
int tot=1;
for(int i=2;i<=n;++i)
if(b[i]!=b[i-1]) b[++tot]=b[i];
for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
build();
int last=0;
while(m--){
int l,r;
in>>l>>r;
l^=last,r^=last;
last=query(l,r);
out<<last<<'\n';
}
}
#
因为是 上的题,就简单点好了。给出一个长度为 的序列,给出 个询问:在 之间找到一个在这个区间里只出现过一次的数,并且要求找的这个数尽可能大。如果找不到这样的数,则直接输出 。
强制在线
思路:
嗯,这个题显然无法用树状数组/线段树维护,考虑分块 我是不会用树套树的
用 表示块长
考虑我们可以用 维护每个权值出现的所有位置从而找到每个值上一次出现的位置 和下一次出现的位置
显然对于每一个数 它对 这个范围由贡献
考虑对于每个数 要对区间 有贡献当且仅当:
- 且
对边角块每一个数直接暴力判断其 和 是否不在区间内即可,这部分的时间复杂度为
对整块 我们显然只用考虑在 内,前 大的只出现一次的数
因为边角块最多有 个数,显然这 个数里面至少有一个数在 内只出现过一次,必然会造成贡献
我们用 表示块 中第 大的只出现一次的数,显然我们可以考虑每一个数值对所有整块的贡献
这部分时间复杂度 ,空间复杂度
然后整块的每一个数的判断和边角块的每一个数的判断是一样的
当 取 这题的时间复杂度就为 ,空间复杂度为 ,可以通过
这里我 取的
int n,m,sz,a[N],p[N];
int bl[N],d[N],L[S],R[S];
int f[S][S][2*S],id[S][S][2*S],cnt[S][S],sum[N];
vector <int> v[N];
inline void build(){
for(int i=n;i;--i){
for(int j=0;j<v[i].size();++j){
int nb=bl[v[i][j]];
int l=1,r=bl[n];
if(j) l=bl[v[i][j-1]]+1;
if(j<v[i].size()-1) r=bl[v[i][j+1]]-1;
for(int k=l;k<=nb;++k)
for(int q=nb;q<=r;++q){
if(cnt[k][q]>sz*2) continue;
f[k][q][++cnt[k][q]]=i;
id[k][q][cnt[k][q]]=v[i][j];
}
}
}
}
inline int query(int x,int y){
int l=bl[x],r=bl[y];
int mx=0,ct=0;
if(l==r){
for(int i=x;i<=y;++i){
++sum[a[i]];
if(sum[a[i]]==1) d[++ct]=a[i];
}
for(int i=1;i<=ct;++i){
if(sum[d[i]]==1) mx=max(mx,d[i]);
sum[d[i]]=0,d[i]=0;
}
return mx;
}
int tot=cnt[l+1][r-1];
for(int i=1;i<=tot;++i){
int now=f[l+1][r-1][i],pos=id[l+1][r-1][i];
if((p[pos]==0||(p[pos]&&v[now][p[pos]-1]<x))&&(p[pos]==v[now].size()-1||(p[pos]<v[now].size()-1&&v[now][p[pos]+1]>y))){
mx=now;
break;
}
}
for(int i=x;i<=R[l];++i){
int now=a[i];
if((p[i]==0||(p[i]&&v[now][p[i]-1]<x))&&(p[i]==v[now].size()-1||(p[i]<v[now].size()-1&&v[now][p[i]+1]>y)))
mx=max(mx,a[i]);
}
for(int i=L[r];i<=y;++i){
int now=a[i];
if((p[i]==0||(p[i]&&v[now][p[i]-1]<x))&&(p[i]==v[now].size()-1||(p[i]<v[now].size()-1&&v[now][p[i]+1]>y)))
mx=max(mx,a[i]);
}
return mx;
}
signed main(){
read(n),read(m);
sz=300;
for(int i=1;i<=n;++i){
read(a[i]);
bl[i]=(i-1)/sz+1;
if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
v[a[i]].push_back(i);
p[i]=v[a[i]].size()-1;
}
R[bl[n]]=n;
build();
int lastans=0;
while(m--){
int x,y;
read(x),read(y);
x=(x+lastans)%n+1,y=(y+lastans)%n+1;
if(x>y) swap(x,y);
lastans=query(x,y);
write(lastans);
putchar('\n');
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具