分块二分:
众所周知,分块可以实现一些奇奇怪怪的区间的问题,比如以下这个题:
给定一个 \(N\) 和 \(N\) 个整数,分别为 \(A_1,A_2,A_3,A_4....A_N\) 每次询问给出三个整数 \(l,r,k\) 求所有满足 \(l \leq i \leq r,a_i \leq k\) 的个数。
不难想到暴力,可以做到每次操作 \(N \log_2 N\) 总计 \(MN \log_2 N\) 但这样的复杂度在 \(N,M \leq 100000\) 时是不容易过掉的。因此考虑优化它。
如果用分块该如何实现呢?
可以把每一个块内的元素排序,然后整块时直接二分答案,散块暴力处理,这样就能将时间复杂度降至 \(O(Q\sqrt N \log_2 N)\) 但这样是不容易过的,因此继续考虑优化它,不难想到,当整块时,块长越大,\(\log_2 (r-l+1)\) 是基本不变的,但是可以减少块的数量,因此我们可以将块长设为 \(N/\sqrt{N\log N}\) 这样做复杂度可以近似 \(O(N\sqrt{Q \log_2 N})\) 可以通过此题。
样例:
text
5 3
1 2 3 4 5
1 5 3
1 4 3
2 5 2
block.out
text
3
3
1
代码:
cpp
//writer : tomxi
#include<cstdio>
#include<algorithm>
#include<iostream>
using std::sort;
const int N=1e5+5;
const int K=300;
int a[N],d[N],n,q,l,r,left,right,lk,rk,belong[N],L[K],R[K],len,k;
inline int query(int lf,int rt,int val){
lk=belong[lf],rk=belong[rt];
int ans=0;
if(lk==rk){
for(int i=lf;i<=rt;i++) if(a[i]<=val) ans++;
return ans;
}
for(int i=lf;i<L[lk+1];i++) if(a[i]<=val) ans++;
for(int i=rt;i>R[rk-1];i--) if(a[i]<=val) ans++;
for(int i=lk+1;i<=rk-1;i++){
left=L[i],right=R[i];
int res=0;
while(left<=right){
int mid=(left+right)>>1;
if(d[mid]<=k){
res=mid-L[i]+1;
left=mid+1;
}else{
right=mid-1;
}
}
ans+=res;
}
return ans;
}
int main(){
scanf("%d%d",&n,&q);
len=sqrt(n*25);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) d[i]=a[i];
for(int i=1;i<=n;i++) belong[i]=(i-1)/len+1;
for(int i=1,j=n;i<=n;i++,j--){
R[belong[i]]=i;L[belong[j]]=j;
}
for(int i=belong[1];i<=belong[n];++i) sort(d+L[i],d+R[i]+1);
while(q--){
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",query(l,r,k));
}
return 0;
}