分块打表

这是什么?

很多时候,我们会遇到这种题:

给出一个区间 \([L,R]\),求出该区间内满足 某条件 的数的个数,\(0 \le L,R \le 2\times10^9\)

显然,暴力是过不去的,而如果我们打表打出前缀和数组,则会因代码过长而交不上去;
所以,我们可以将这两者的优点结合起来,这就是——分块打表

怎么做?

\(T\) 为块长,预处理出一个 \(pre\) 数组,\(pre_i\) 表示(即前缀和)第 \(i\) 块的答案数量;
那么如果 \(L\)\(R\) 在同一块里,直接暴力;否则整块取出预处理好的的答案,两边零散块暴力。
复杂度 \(O(T \ \cdot n)\)\(n\) 为单次判断的复杂度。
显然块长越小越好,但减小块长的代价是增加码长,所以应选出一个合适的块长。
一般来讲,对于 \(2\times10^9\) 的值域,块长取 \(5\times10^5\) 是一个不错的选择。

打表代码:

#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=2e9;
int check(int x){
	/*这里是对一个数是否符合要求的判断*/
}
int cnt,ans;
main(){
	cout<<"int pre[]={0";
	int len=5e5;//len 为块长,可以找一个最适合自己的
	for(int i=1;i<=N;i++){
		ans+=check(i);
		if(++cnt==len){
			cout<<","<<ans;
			cnt=0;
		}
	}
	cout<<"};";
	return 0;
}

提交代码:

#include<bits/stdc++.h>
#define int ll 
using namespace std;
typedef long long ll;
int l,r;
int pre[]={/*这里是你打好的表*/};
int check(int x){
	/*这里是对一个数是否符合要求的判断*/
}
int getans(int l,int r){//计算答案 
	if(l>r)return 0;
	int ans=0;
	int L=(l/500000),R=(r/500000);
	if(L==R){
		for(int i=l;i<=r;i++)ans+=check(i);
		return ans;
	}
	ans=pre[R]-pre[L];
	for(int i=L*500000+1;i<=l-1;i++)ans-=check(i);
	for(int i=R*500000+1;i<=r;i++)ans+=check(i);
	return ans;
}
main(){
	cin>>l>>r;
	cout<<getans(l,r);
	return 0;
}
posted @ 2024-06-19 10:37  萝卜甜了  阅读(191)  评论(0)    收藏  举报