分块学习笔记

分块是一种 思想
它用于存在区间问题,并且结合律不是简单相加的时候
线段树和树状数组此时不可用,这个时候,我们就要使用分块

切入正题

分块思想是这样的
设数组长度是n,共有q块,把数组存下来,分成O(nq)段,每段内存一个tag表示区间标记,当区间修改的时候,对于整块,直接增加标记即可。
很类似线段树的"标记永久化",思想都是一样的,不过实现方法有很大差别。
而对于不满一块的情况下,直接暴力运算即可。

使用条件

可以快速算出一个操作对单独的数的影响。
块与块
一般情况下,n,m 200000
当操作简单或者数据水时,可以到500000

时间复杂度分析

当块长为O(nq)的时候,对于查询,直接O(1)查询数组即可,注意加上tag。
对于区间修改,在块内的修改,显然最多把n个数全都改了,时间复杂度O(q)
在单块的暴力修改,最多修改两个块长-2,时间复杂度2*n/q-2=nq
∴时间复杂度为O(n/q+q);由均值不等式知,当q=n时,单次时间复杂度取最小值O(n).
附加:对于基础区间查询
区间查询:类似区间加法,暴力统计左右不完整块的答案,然后统计完整块。时间复杂度:O(n).
为了方便,我们都默认下文的分块大小为n

例题(更新中)

例1:分块1
分块模板题,单点修改,区间查询
既然是模板题,非常好想,只要仔细阅读本文前面就可以轻松AC
注意!!!!开快读,因为原来是树状数组的题,所以卡常!
附上代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,len;
int v[50005],b[50005],tag[50005];
void add(int l,int r,int d) {
	for(int i=l;i<=min(b[l]*len,r);i++)v[i]+=d;
	if(b[l]!=b[r]) {
		for(int i=(b[r]-1)*len+1;i<=r;i++)v[i]+=d;
	}
	for(int i=b[l]+1;i<=b[r]-1;i++)tag[i]+=d;
}
int main() {
	cin>>n;len=sqrt(n);
	for(int i=1;i<=n;i++)cin>>v[i];
	for(int i=1;i<=n;i++)b[i]=(i-1)/len+1;
	for(int i=1;i<=n;i++){
		int c,l,r,d;
		cin>>c>>l>>r>>d;
		if(c==0)add(l,r,d);
		if(c==1)cout<<v[r]+tag[b[r]]<<'\n';
	}
	return 0;
}

例2:分块2
这道题就是区间修改+区间查询。
不需要开快读。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,len,b[120005],m;
ll v[120005],tag[120005],sum[120005];
void add(int l,int r,ll d){
    for(int i=l;i<=min(b[l]*len,r);i++){
		v[i]+=d;sum[b[l]]+=d;
	}
    if(b[l]!=b[r])
        for(int i=(b[r]-1)*len+1;i<=r;i++)
            v[i]+=d,sum[b[r]]+=d;
    for(int i=b[l]+1;i<=b[r]-1;i++)tag[i]+=d;
}
ll ask(int l,int r){
    ll ans=0;
    for(int i=l;i<=min(b[l]*len,r);i++)ans+=v[i]+tag[b[l]];
    if(b[l]!=b[r])
        for(int i=(b[r]-1)*len+1;i<=r;i++)
            ans+=v[i]+tag[b[r]];
    for(int i=b[l]+1;i<=b[r]-1;i++)ans+=sum[i]+len*tag[i];
    return ans;
}
int main(){
    n=read();m=read();len=sqrt(n);
    for(int i=1;i<=n;++i)v[i]=read();
    for(int i=1;i<=n;++i){
        b[i]=(i-1)/len+1;sum[b[i]]+=v[i];
    }
    for(int i=1;i<=m;++i){
        int c=read(),l=read(),r=read();
        if(c==1){
        	int d=read();add(l,r,d);
        }
        if(c==2)printf("%lld\n",ask(l,r));
    }
    return 0;
}

例3:分块3
给出一个长n的数组,以及n个操作,涉及区间加法,询问区间内小于某个值x的元素个数。
1不完整的块暴力枚举即可
2每个块内需要有序,所以在每次区间加法之后需要对端点所在的两个块重新排序
3每次查询在块内二分,以及暴力枚举两个端点所在块的元素。
时间复杂度证明:
设块长为q,则总共nq个块
预处理排序,复杂度O(nlogn)
对于区间修改操作,仍然是维护加法tag,暴力修改两边的块+重新排序,复杂度O(qlognq)
对于区间查询操作,最坏在O(nq)个块内二分,以及暴力枚举O(q)个元素,复杂度O(q+nqlogq)

总复杂度O(nlogn+nqlognq+n2logqq)
一般取q=n即可,此时复杂度O(nnlogn)
但是,如果将q取稍些,复杂度会更优,由于证明较为复杂,不再提及。
upd:经过测试,当q的值大约为nloglogn)的时候,常数较小。
不知道你们的电脑是怎么样的反正我的和LOJ的评测机都是这样
不过此题卡常,建议快读。
代码实现:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,len,m;
int v[500005],b[500005],tag[500005];
vector<int> g[505];
inline void reset(int x){
	g[x].clear();
	for(int i=(x-1)*len+1;i<=min(x*len,n);++i)g[x].push_back(v[i]);
	sort(g[x].begin(),g[x].end());
}
inline void add(int l,int r,int d) {
	for(register int i=l;i<=min(b[l]*len,r);++i)v[i]+=d;
	reset(b[l]);
	if(b[l]!=b[r]) {
		for(register int i=(b[r]-1)*len+1;i<=r;++i)v[i]+=d;
		reset(b[r]);
	}
	for(register int i=b[l]+1;i<=b[r]-1;++i)tag[i]+=d;
}
inline int ask(int l,int r,int d){
	int ans=0;
	for(register int i=l;i<=min(b[l]*len,r);++i){
		if(v[i]+tag[b[l]]<d)++ans;
	}
	if(b[l]!=b[r]){
		for(register int i=(b[r]-1)*len+1;i<=r;++i)
			if(v[i]+tag[b[r]]<d)ans++;
	}
	for(register int i=b[l]+1;i<=b[r]-1;++i){
		int x=d-tag[i];
		ans+=lower_bound(g[i].begin(),g[i].end(),x)-g[i].begin();
	}
	return ans;
}
int main() {
    ios::sync_with_stdio(0);cin.tie(0);
	n=read();len=sqrt(n);
	for(register int i=1;i<=n;++i)v[i]=read();
	for(register int i=1;i<=n;++i){
		b[i]=(i-1)/len+1;
		g[b[i]].push_back(v[i]);
	}
	for(register int i=1;i<=b[n];++i)
        sort(g[i].begin(),g[i].end());
	for(register int i=1;i<=n;++i){
		int c=read(),l=read(),r=read(),d=read();
		if(c==0){
		    add(l,r,d);
		}
		if(c==1)cout<<ask(l,r,d*d)<<'\n';
	}
	return 0;
}

正在更新中……

posted @   JZX102624  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示