分块

本文深入探讨了分块算法在处理区间操作和查询时的应用,包括区间加法、区间乘法、单点查询等多种操作,以及如何通过维护块的标记和状态来优化查询效率。文章详细解释了每种情况下分块的具体实现,如使用tag标记、维护区间和、离散化等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接
https://loj.ac/problems/search?keyword=分块

参考博客:http://hzwer.com/8053.html

1.区间加法,单点查值
完整的块打个tag标记,非完整直接暴力修改

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;

int n,block;
int tag[50005],bl[50005],v[50005];

void add(int l,int r,int c)
{
	for(int i=l;i<=min(bl[l]*block,r);i++) v[i]+=c;
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*block+1;i<=r;i++) v[i]+=c;
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]+=c;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1;
	for(int i=1;i<=n;i++)
	{
		int f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r,c);
		else
			cout<<v[r]+tag[bl[r]]<<endl;
	}

}

2.区间加法,询问区间内小于某个值x的元素个数
每个区间vector维护有序序列,完整区间直接二分查询,未完整暴力查询

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;

int n,block;
int tag[50005],bl[50005],a[50005];
vector<int> v[50006];
void reset(int x)
{
	v[x].clear();
	for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
	{
		v[x].push_back(a[i]);
	}
	sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c)
{
	for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;
	reset(bl[l]);
	if(bl[l]!=bl[r])
	{
		for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;
		reset(bl[r]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]+=c;
}
int query(int l,int r,int c)
{
	int ans=0;
	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		if(a[i]+tag[bl[l]]<c) ans++;
	}
	if(bl[l]!=bl[r])
	{
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
			if(a[i]+tag[bl[r]]<c) ans++;
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	{
		int x=c-tag[i];
		ans+=lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin();
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		bl[i]=(i-1)/block+1;
		v[bl[i]].push_back(a[i]);
	}
	for(int i=1;i<=bl[n];i++)
		sort(v[i].begin(),v[i].end());
	for(int i=1;i<=n;i++)
	{
		int f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r,c);
		else
			cout<<query(l,r,c*c)<<endl;
	}

}

3.区间加法,询问区间内小于某个值 x的前驱(比其小的最大元素)。
同理每个块维护有序区间,完整二分,两边暴力

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e5 + 10;
const ll mod = 1000000007;
const int base=131;

int n,block;
int tag[N],bl[N],a[N];
vector<int> v[N];
void reset(int x)
{
	v[x].clear();
	for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
	{
		v[x].push_back(a[i]);
	}
	sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c)
{
	for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;
	reset(bl[l]);
	if(bl[l]!=bl[r])
	{
		for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;
		reset(bl[r]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]+=c;
}
int query(int l,int r,int c)
{
	int ans=-1;
	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		if(a[i]+tag[bl[l]]<c) ans=max(ans,a[i]+tag[bl[l]]);
	}
	if(bl[l]!=bl[r])
	{
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
			if(a[i]+tag[bl[r]]<c)
				ans=max(ans,a[i]+tag[bl[r]]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	{
		int x=c-tag[i];
		auto it=lower_bound(v[i].begin(),v[i].end(),x);
		if(it==v[i].begin()) continue;
		--it;
		ans=max(ans,*it+tag[i]);
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		bl[i]=(i-1)/block+1;
		v[bl[i]].push_back(a[i]);
	}
	for(int i=1;i<=bl[n];i++)
		sort(v[i].begin(),v[i].end());
	for(int i=1;i<=n;i++)
	{
		int f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r,c);
		else
			cout<<query(l,r,c)<<endl;
	}

}

4.区间加法,区间求和

维护每个块的和,完整时直接+block*tag,非完整暴力修改

#include<bits/stdc++.h>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;

ll n,block;
ll tag[50005],bl[50005],v[50005],num[50005];

void add(ll l,ll r,ll c)
{
	for(int i=l;i<=min(bl[l]*block,r);i++) v[i]+=c,num[bl[i]]+=c;
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*block+1;i<=r;i++) v[i]+=c,num[bl[i]]+=c;
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]+=c;
}
ll query(ll l,ll r,ll c)
{
	ll ans=0;
	for(int i=l;i<=min(bl[l]*block,r);i++) ans=(ans+v[i]+tag[bl[i]])%(c+1);
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*block+1;i<=r;i++) ans=(ans+v[i]+tag[bl[i]])%(c+1);
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		ans=(ans+block*tag[i]+num[i])%(c+1);
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1,num[bl[i]]=num[bl[i]]+v[i];
	//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	for(int i=1;i<=n;i++)
	{
		ll f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r,c);
		else
			cout<<query(l,r,c)<<endl;
		//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	}

}

5.区间开方,区间求和
开方操作会在有限次变为0,所以我们只需要用一个tag标记每一块是否为0,如果不为0,暴力修改每一块,并且维护每一块的和,如果为0就直接返回

#include<bits/stdc++.h>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;

ll n,block;
ll tag[50005],bl[50005],v[50005],num[50005],flag[50005];

void kai(ll x)
{
	if(flag[x]) return ;
	flag[x]=1;
	num[x]=0;
	for(int i=(x-1)*block+1;i<=x*block;i++)
	{
		v[i]=sqrt(v[i]),num[x]+=v[i];
		if(v[i]>1) flag[x]=0;
	}
}
void add(ll l,ll r,ll c)
{

	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		num[bl[i]]-=v[i];
		v[i]=sqrt(v[i]);
		num[bl[i]]+=v[i];
	}
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
		{
			num[bl[i]]-=v[i];
			v[i]=sqrt(v[i]);
			num[bl[i]]+=v[i];
		}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		kai(i);
}
ll query(ll l,ll r)
{
	ll ans=0;
	for(int i=l;i<=min(bl[l]*block,r);i++) ans+=v[i];
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*block+1;i<=r;i++) ans+=v[i];
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		ans=ans+num[i];
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1,num[bl[i]]=num[bl[i]]+v[i];
	//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	for(int i=1;i<=n;i++)
	{
		ll f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r,c);
		else
			cout<<query(l,r)<<endl;
		//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	}

}

6.单点插入,单点询问,数据随机生成
关于插入操作,如果插入过多可能会使块变得很大,影响块的查询时间复杂度,所以我们考虑超过一定数量,直接重构整个块
我们考虑每 n \sqrt{n} n 次插入后,重构整个块,重构的时间复杂度是n,那么总共也就是 n n n\sqrt{n} nn 的时间,这个时间复杂度是可接受的

#include<bits/stdc++.h>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 1000000007;
const int base=131;

ll n,block,m;
ll tag[N],bl[N],tmp[N],a[N];
vector<int> v[N];

P query(int x)
{
	int k=1;
	while(x>v[k].size())
		x-=v[k].size(),k++;
	return make_pair(k,x-1);
}

void rebuild()
{
	int cnt=0;
	for(int i=1;i<=m;i++)
	{
		for(int x:v[i]) tmp[++cnt]=x;
		v[i].clear();
	}
	int blo2=sqrt(cnt);
	for(int i=1;i<=cnt;i++)
	{
		v[(i-1)/blo2+1].push_back(tmp[i]);
	}
	m=(n-1)/blo2+1;
}
void add(ll l,ll r)
{
	P now=query(l);
	v[now.fi].insert(v[now.fi].begin()+now.se,r);
	if(v[now.fi].size()>=20*block)
		rebuild();
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) v[(i-1)/block+1].push_back(a[i]);
	m=(n-1)/block+1;
	//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	for(int i=1;i<=n;i++)
	{
		ll f ,l ,r, c;
		cin>>f>>l>>r>>c;
		if(f==0) add(l,r);
		else
		{
			P t=query(r);
			cout<<v[t.fi][t.se]<<endl;
		}
		//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
	}

}

7.区间乘法,区间加法,单点询问
考虑维护两个标记,维护一个乘法标记,一个加法标记
让乘法标记优先级高于加法标记
具体意思就是乘法标记能同时让乘法和加法标记翻倍
加法标记直接加在加法标记上

#include<bits/stdc++.h>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;

ll n,block,m;
ll tag[N],bl[N],tagmul[N],a[N];
vector<int> v[N];

void reset(int x)
{
	for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
	{
		a[i]=(a[i]*tagmul[x]+tag[x])%mod;
	}
	tagmul[x]=1,tag[x]=0;
}
void add(ll l,ll r,ll c)
{
	reset(bl[l]);
	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		a[i]=(a[i]+c)%mod;
	}
	if(bl[l]!=bl[r])
	{
		reset(bl[r]);
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
		a[i]=(a[i]+c)%mod;
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]=(tag[i]+c)%mod;
}

void mul(ll l,ll r,ll c)
{
	reset(bl[l]);
	for(int i=l;i<=min(bl[l]*block,r);i++)

		a[i]=(a[i]*c)%mod;

	if(bl[l]!=bl[r])
	{
		reset(bl[r]);
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
		a[i]=(a[i]*c)%mod;
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
		tag[i]=(tag[i]*c)%mod,tagmul[i]=(tagmul[i]*c)%mod;
}

ll query(ll x)
{
	return (a[x]*tagmul[bl[x]]+tag[bl[x]])%mod;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>a[i],a[i]%=10007;
	for(int i=1;i<=n;i++)
	{
		tagmul[i]=1;
		bl[i]=(i-1)/block+1;
	}
	for(int i=1;i<=n;i++)
	{
		int opt,l,r,c;
		cin>>opt>>l>>r>>c;
		if(!opt)
		{
			add(l,r,c);
		}
		else if(opt&1)
		{
			mul(l,r,c);
		}
		else cout<<query(r)%mod<<endl;
		//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
	}

}

8.操作涉及区间询问等于一个数 的元素,并将这个区间的所有元素改为 。

询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了
维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。
换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。

#include<bits/stdc++.h>
#define fi first
#define se second
#define FOR(a) for(int i=0;i<a;i++)
#define sc(a) scanf("%d",&a)
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;

ll n,block,m;
ll tag[N],bl[N],tagmul[N],a[N];
vector<int> v[N];
void reset(int x)
{
	if(tag[x]==-1) return ;
	for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
	{
		a[i]=tag[x];
	}
	tag[x]=-1;
}
ll kk(int x,int c)
{
	int ans=0;
	for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
	{
		if(a[i]==c)
		{
			ans++;
		}
		else a[i]=c;
	}
	tag[x]=c;
	return ans;
}
ll query(ll l,ll r,ll c)
{
	ll ans=0;
	reset(bl[l]);
	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		if(a[i]==c) ans++;
		else a[i]=c;
	}

	if(bl[l]!=bl[r])
	{
		reset(bl[r]);
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
		{
			if(a[i]==c) ans++;
			else a[i]=c;
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	{
		if(tag[i]==-1)
		{
			ans+=kk(i,c);
		}
		else if(tag[i]==c) ans+=block;
		else tag[i]=c;
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	block=sqrt(n);
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		bl[i]=(i-1)/block+1;
	}
	for(int i=1;i<=bl[n];i++)
	{
		int st=(bl[i]-1)*block+1;
		tag[i]=a[st];
		for(int j=(bl[i]-1)*block+1;j<=min(bl[i]*block,n);j++)
		{
			if(a[j]!=a[st])
			{
				tag[i]=-1;
				break;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		int opt,l,r,c;
		cin>>l>>r>>c;
		cout<<query(l,r,c)<<endl;
		//update(l,r,c);
		//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
	}

}

9.询问区间的最小众数
这道题目是强制在线的,考虑分块情况,众数只可能出现在完整块中,或者左右两边的数中,所以我们用 n n n\sqrt{n} nn 预处每个块的最小众数,并且离散化后将每个数的下标存入对应的vector中,这样我们查询数量时,可以直接二分该树的vector下标范围查询数量了,对于这道题块大小为50跑得最快,我也不知道为什么…

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;

int n,block,m,id;
int tag[N],bl[N],val[N],a[N];
int cal[N],num[N];
vector<int> v[N];
unordered_map<int,int> mp;
int f[2005][2005];
void pre(int x)
{
	int k=0;
	for(int i=1;i<=n;i++) num[i]=0;
	for(int i=(x-1)*block+1;i<=n;i++)
	{
		num[a[i]]++;
		if(num[a[i]]>num[k]||(num[k]==num[a[i]]&&val[a[i]]<val[k]))	k=a[i];
		f[x][bl[i]]=k;
	}
}
int qnum(int l,int r,int x)
{
	int t=upper_bound(v[x].begin(),v[x].end(),r)-lower_bound(v[x].begin(),v[x].end(),l);
	return t;
}
int query(int l,int r)
{
	int ans,mx;
	ans=f[bl[l]+1][bl[r]-1];
	mx=qnum(l,r,ans);
	for(int i=l;i<=min(bl[l]*block,r);i++)
	{
		int x=qnum(l,r,a[i]);
		if(x>mx||(x==mx&&val[a[i]]<val[ans])) ans=a[i],mx=x;
	}
	if(bl[l]!=bl[r])
	{
		for(int i=(bl[r]-1)*block+1;i<=r;i++)
		{
			int x=qnum(l,r,a[i]);
			if(x>mx||(x==mx&&val[a[i]]<val[ans])) ans=a[i],mx=x;
		}
	}
	return val[ans];
}
int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);
//	cout.tie(0);
	scanf("%d",&n);
	//cout<<log2(n)<<endl;
	block=50;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		bl[i]=(i-1)/block+1;
		if(!mp[a[i]]) mp[a[i]]=++id,val[id]=a[i];
		a[i]=mp[a[i]];
		v[a[i]].push_back(i);
	}
	for(int i=1;i<=bl[n];i++)
	{
		pre(i);
	}
	for(int i=1;i<=n;i++)
	{
		int l,r,c;
		scanf("%d %d",&l,&r);
		printf("%d\n",query(l,r));
		//update(l,r,c);
		//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值