分块
大家好,我非常喜欢暴力数据结构
就是把数组分成 \(\lceil \sqrt n \rceil\) 块,每一块的长度最大是 \(\lfloor \sqrt n \rfloor\)
左端点是 \(L_i\) ,右端点是 \(R_i\), \(i\) 所在的块是 \(id_i\)
对于一个区间查询 \([l,r]\),分成三个部分
首先 \(p=id_l,q=id_r\)
- 若 \(p=q\) 直接暴力
- 若 \(p\ne q\)
- \([l,R_p]\) 暴力处理
- \([L_q,r]\) 暴力处理
- 第 \((p,q)\) 块,用懒标记或二分
时间复杂度 \(\sqrt n\)
使用范围: \(n<10^6\) ,或者代替一些高级的大常数数据结构
代替线段树
例:POJ - 3468:区间加,区间求和
边角暴力,整块打上懒标记即可
#include<cstdio>
#include<cmath>
using namespace std;
const int N=100005;
typedef long long LL;
int n,T,L[N],R[N],pos[N],tt;
LL x[N],tg[N],sm[N];
char opt[5];
inline void Change(int l,int r,LL vl) {
register int p=pos[l],q=pos[r];
if(p==q) {
for(int i=l;i<=r;i++)x[i]+=vl;
sm[p]+=vl*(r-l+1);
return;
}
for(int i=p+1;i<=q-1;i++)tg[i]+=vl;
for(int i=l;i<=R[p];i++)x[i]+=vl;
sm[p]+=vl*(R[p]-l+1);
for(int i=L[q];i<=r;i++)x[i]+=vl;
sm[q]+=vl*(r-L[q]+1);
}
inline LL Ask(int l,int r) {
register int p=pos[l],q=pos[r];
register LL res=0;
if(p==q) {
for(int i=l;i<=r;i++)res+=x[i];
res+=tg[p]*(r-l+1);
return res;
}
for(int i=p+1;i<=q-1;i++)
res+=sm[i]+tg[i]*(R[i]-L[i]+1);
for(int i=l;i<=R[p];i++)res+=x[i];
res+=tg[p]*(R[p]-l+1);
for(int i=L[q];i<=r;i++)res+=x[i];
res+=tg[q]*(r-L[q]+1);
return res;
}
int main() {
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++)scanf("%lld",&x[i]);
tt=sqrt(1.0*n);
for(int i=1;i<=tt;i++) {
L[i]=(i-1)*tt+1;
R[i]=i*tt;
}
if(R[tt]<n)++tt,L[tt]=R[tt-1]+1,R[tt]=n;
for(int i=1;i<=tt;i++)
for(int j=L[i];j<=R[i];j++)
pos[j]=i,sm[i]+=x[j];
for(int l,r;T--;) {
LL v;
scanf("%s%d%d",opt,&l,&r);
if(opt[0]=='C')scanf("%lld",&v),Change(l,r,v);
else printf("%lld\n",Ask(l,r));
}
}
代替主席树
例:Dynamic Rankings:带修主席树,查找区间第 \(k\) 大
块内排序,每次修改也排
二分答案,\(check\) 是 \(mid\) 大的数的个数是否比 \(k\) 大
至于查询:边角暴力,块内二分
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,T,sq,x[N],y[N],id[N],L[N],R[N];
char op[5];
inline void bui(int p) {
for(int i=L[p];i<=R[p];i++)
y[i]=x[i];
sort(y+L[p],y+R[p]+1);
}
inline void mdy(int p,int v) {
x[p]=v,bui(id[p]);
}
inline int get(int p,int v) {
register int LL=L[p],RR=R[p];
register int mid,ans=-1;
while(LL<=RR) {
mid=LL+RR>>1;
if(y[mid]<=v)
ans=mid,LL=mid+1;
else RR=mid-1;
}
return ~ans?ans-L[p]+1:0;
}
inline int Ask(int l,int r,int k) {
register int LL=0,RR=1e9,mid,ans=1;
register int p=id[l],q=id[r],cnt;
while(LL<=RR) {
mid=LL+RR>>1,cnt=0;
if(p==q) {
for(int i=l;i<=r;i++)
if(x[i]<=mid)++cnt;
} else {
for(int i=l;i<=R[p];i++)
if(x[i]<=mid)++cnt;
for(int i=L[q];i<=r;i++)
if(x[i]<=mid)++cnt;
for(int i=p+1;i<q;i++)
cnt+=get(i,mid);
}
if(cnt>=k)
ans=mid,RR=mid-1;
else LL=mid+1;
}
return ans;
}
int main() {
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++)scanf("%d",&x[i]);
sq=sqrt(n);
for(int i=1;i<=sq;i++)L[i]=R[i-1]+1,R[i]=i*sq;
if(R[sq]<n)++sq,L[sq]=R[sq-1]+1,R[sq]=n;
for(int i=1;i<=sq;i++) {
for(int j=L[i];j<=R[i];j++)
id[j]=i;
bui(i);
}
for(int l,r,k;T--;) {
scanf("%s",op);
if(op[0]=='C') {
scanf("%d%d",&l,&r);
mdy(l,r);
} else {
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",Ask(l,r,k));
}
}
}
代替平衡树
来一波刺激的:代替树套树
例 二逼平衡树
如果上面两题都没问题了,这题只需要时间
排名、前驱后继都是块内二分,修改和第 \(K\) 大同上
调了一个晚修。。。
#include<bits/stdc++.h>
#define fo(i,a,b) for(register int i=(a);i<=(b);i++)
using namespace std;
char buf[100000],*S=buf,*T=buf;
inline char G() { return S==T&&(T=(S=buf)+fread(buf,1,100000,stdin),S==T)?EOF:*S++; }
inline int Rd() {
register int x=0; char C=G();
for(;C<'0'||C>'9';C=G());
for(;C>'/'&&C<':';C=G())x=(x<<1)+(x<<3)+(C^48);
return x;
}
inline void Wr(int x) { if(x>9)Wr(x/10); putchar(x%10+48); }
inline void Out(int x) { if(x<0)putchar(45),x=-x; Wr(x),putchar(10); }
const int N=50005; int n,TT,x[N],y[N],id[N],L[N],R[N],sq,op,l,r,k;
inline void bui(int p) { fo(i,L[p],R[p])y[i]=x[i]; sort(y+L[p],y+R[p]+1); }
inline void mdy(int p,int v) { x[p]=v,bui(id[p]); }
inline int low(int p,int v) {
register int LL=L[p],RR=R[p],mid,ans=L[p]-1;
while(LL<=RR) {
mid=LL+RR>>1;
if(y[mid]<v)ans=mid,LL=mid+1;
else RR=mid-1;
} return ans-L[p]+1;
}
inline int get(int p,int v) {
register int LL=L[p],RR=R[p],mid,ans=L[p]-1;
while(LL<=RR) {
mid=LL+RR>>1;
if(y[mid]<=v)ans=mid,LL=mid+1;
else RR=mid-1;
} return ans-L[p]+1;
}
inline int rnk() {
register int p=id[l],q=id[r],res=1;
if(p==q) {
fo(i,l,r)res+=(x[i]<k);
return res;
}
fo(i,l,R[p])res+=(x[i]<k);
fo(i,L[q],r)res+=(x[i]<k);
fo(i,p+1,q-1)res+=low(i,k);
return res;
}
inline int kth() {
register int LL=0,RR=1e8,mid,ans=1,p=id[l],q=id[r],cnt;
while(LL<=RR) {
mid=LL+RR>>1,cnt=0;
if(p==q) {
fo(i,l,r)cnt+=(x[i]<=mid);
} else {
fo(i,l,R[p])cnt+=(x[i]<=mid);
fo(i,L[q],r)cnt+=(x[i]<=mid);
fo(i,p+1,q-1)cnt+=get(i,mid);
}
if(cnt>=k)ans=mid,RR=mid-1;
else LL=mid+1;
}
return ans;
}
inline int pre() {
register int p=id[l],q=id[r],ans=-2147483647,LL,RR,mid,res;
if(p==q) {
fo(i,l,r)if(x[i]<k)ans=max(ans,x[i]);
return ans;
}
fo(i,l,R[p])if(x[i]<k)ans=max(ans,x[i]);
fo(i,L[q],r)if(x[i]<k)ans=max(ans,x[i]);
fo(i,p+1,q-1) {
LL=L[i],RR=R[i],res=-1;
while(LL<=RR) {
mid=LL+RR>>1;
if(y[mid]<k)res=mid,LL=mid+1;
else RR=mid-1;
}
if(~res && y[res]<k)ans=max(ans,y[res]);
}
return ans;
}
inline int suc() {
register int p=id[l],q=id[r],ans=2147483647,LL,RR,mid,res;
if(p==q) {
fo(i,l,r)if(x[i]>k)ans=min(ans,x[i]);
return ans;
}
fo(i,l,R[p])if(x[i]>k)ans=min(ans,x[i]);
fo(i,L[q],r)if(x[i]>k)ans=min(ans,x[i]);
fo(i,p+1,q-1) {
LL=L[i],RR=R[i],res=-1;
while(LL<=RR) {
mid=LL+RR>>1;
if(y[mid]>k)res=mid,RR=mid-1;
else LL=mid+1;
}
if(~res && y[res]>k)ans=min(ans,y[res]);
}
return ans;
}
int main() {
n=Rd(),TT=Rd(),sq=sqrt(n);
fo(i,1,n)x[i]=Rd();
fo(i,1,sq)L[i]=R[i-1]+1,R[i]=i*sq;
if(R[sq]<n)++sq,L[sq]=R[sq-1]+1,R[sq]=n;
fo(i,1,sq) {
fo(j,L[i],R[i])id[j]=i;
bui(i);
}
while(TT--) {
op=Rd(),l=Rd(),r=Rd();
if(op^3)k=Rd();
if(op==1)Out(rnk());
else if(op==2)Out(kth());
else if(op==3)mdy(l,r);
else if(op==4)Out(pre());
else if(op==5)Out(suc());
}
}
总结
分块虽然暴力,却是一个不错的数据结构
此外,如果数据够小,一些规律题可以用上述方法打表
这就是分块打表,复杂度 \(O(\sqrt n)\)
更重要的是如何运用