进阶分块

分块二分:

众所周知,分块可以实现一些奇奇怪怪的区间的问题,比如以下这个题:

给定一个 \(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})\) 可以通过此题。

样例:

block.in

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;
}