分块(优雅的暴力) 学习博客。。。持续更新

本文深入讲解分块算法在不同场景的应用,包括区间操作、查询优化等核心问题,通过多个实战题目,如逆序对求解、区间加法、区间乘法等,详细解析分块算法的实现细节与技巧。

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

学习参照以下三个博客:分块——优雅的暴力  分块入门1~9 分块数列入门1~9

分块题目训练:

1.逆序对——求区间最小值

分块入门1:

给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,单点查值。

//给定一个长为n的序列,以及n个操作,操作涉及区间加法,单点查值
#include<bits/stdc++.h>
using namespace std;
const int maxn = 50086;
int a[maxn],n,m;
int add[maxn];
int pos[maxn];//,sum[maxn];
int L[maxn],R[maxn];

inline void change(int l,int r,int d){
	int p = pos[l], q = pos[r];
	
	if(p==q)//如果要修改的区间在同一个块里面 
	    for(int i=l;i<=r;++i) a[i]+=d;
	    
	else {//如果要修改的区间不在同一个块里面 
		for(int i=p+1;i<=q-1;++i) add[i] += d;//直接给块赋值 
		for(int i=l;i<=R[p];i++) a[i]+=d;
		for(int i=L[q];i<=r;++i) a[i]+=d;
	}
} 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	int t = sqrt(n);//分为t块 
	
	for(int i=1;i<=t;++i){//开始分块,完整块 
		L[i] = (i-1)*t + 1;
		R[i] = i*t; 
	}
	
	if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;//不完整块 

	for(int i=1;i<=t;i++){//每个元素在块的位置 
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i;
		}
	}
	
	for(int i=1;i<=m;++i){
		int opt, l, r, c;
		cin>>opt;
		if(!opt) {
			cin>>l>>r>>c;
			change(l,r,c);
		}
		else {
			cin>>r;
			int q = pos[r];
			cout<<a[r] + add[q]<<endl;
		}
	}
 } 

昨天才学的分块,今天就出题了,也太爽了吧小牛练习赛E题

 

分块入门2:

给出一个长为 n 的数列,以及 m个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4+100;
int n,m,blo;
int v[maxn],bl[maxn];
 
int atag[maxn];//块的增量 

vector<int>ve[505];//存放每一块的元素 

void reset(int x){//将第x块的元素重新排序 
	ve[x].clear();
	for(int i=(x-1)*blo+1;i<=min(x*blo,n);++i)
	ve[x].push_back(v[i]);
	sort(ve[x].begin(),ve[x].end());
}

void add(int a,int b,int c){//区间更新 
	for(int i=a;i<=min(bl[a]*blo,b);++i) v[i]+=c;
	reset(bl[a]);
	if(bl[a]!=bl[b]){
		for(int i=(bl[b]-1)*blo+1;i<=b;++i) v[i]+=c;
		reset(bl[b]);
	}
	for(int i=bl[a]+1;i<=bl[b]-1;++i) atag[i] += c;
}

int query(int a,int b,int c){ 
	int ans = 0;
	//对于不完整的块直接暴力判断 
	for(int i=a;i<=min(bl[a]*blo,b);i++){
		if(v[i]+atag[bl[a]]<c) ans++;
	}
	if(bl[a]!=bl[b]){
		for(int i=(bl[b]-1)*blo+1;i<=b;++i)
		if(v[i]+atag[bl[b]]<c) ans++;
	}
	//对于完整的块,二分求答案 
	for(int i=bl[a]+1;i<=bl[b]-1;i++){
		int x = c - atag[i];
		ans+=lower_bound(ve[i].begin(),ve[i].end(),x) - ve[i].begin();
	}
	return ans;
}


int main()
{
	cin>>n>>m;
	blo = sqrt(n);
	for(int i=1;i<=n;++i) cin>>v[i];
	for(int i=1;i<=n;++i){
		bl[i] = (i-1)/blo + 1;
		ve[bl[i]].push_back(v[i]);
	}
	for(int i=1;i<=bl[n];++i){
		sort(ve[i].begin(),ve[i].end());//将每一块的元素排序 
	}
	for(int i=1;i<=m;++i){
		int opt,l,r,d;
		cin>>opt>>l>>r>>d;
		if(opt==1) add(l,r,d);
		else printf("%d\n",query(l,r,d));
	}
}

/*10 5
1 2 3 4 5 6 7 8 9 10
1 1 10 1
2 1 10 3
1 1 5 3
1 6 10 4
2 1 10 15
*/

 

 

分块入门3:

给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)。

set会比vector快一点。set插入时间复杂度是o(1),并且set对于删除和插入元素更加灵活。

vector写法:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4+100;
int n,m,blo;
int val[maxn],bl[maxn];
int add[maxn];
vector<int>ve[510];
void reset(int x){
	ve[x].clear();
	for(int i=(bl[x]-1)*blo+1;i<=bl[x]*blo;i++) ve[x].push_back(val[i]);
	sort(ve[x].begin(),ve[x].end());
}
void change(int l,int r,int d){
	for(int i=l;i<=min(r,bl[l]*blo);++i) val[i]+=d;
	reset(bl[l]);
	if(bl[l]!=bl[r]){
		for(int i=(bl[r]-1)*blo+1;i<=r;i++) val[i]+=d;
		reset(bl[r]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++) add[i]+=d;
}
int query(int l,int r,int d){
	int mx = -1;
	for(int i=l;i<=min(r,bl[l]*blo);++i) 
	    if(val[i]+add[bl[l]]<d)  mx =max(mx,val[i]+add[bl[l]]);
	if(bl[l]!=bl[r]){
		for(int i=(bl[r]-1)*blo;i<=r;i++)
		   if(val[i]+add[bl[r]]<d)  mx =max(mx,val[i]+add[bl[r]]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++){
		int x = d - add[i];
		int pos = lower_bound(ve[i].begin(),ve[i].end(),x) - ve[i].begin();
		if(pos==0) continue;
		mx = max(mx,ve[i][pos-1]+add[i]);
	}
	return mx;
}
int main()
{
	cin>>n>>m;blo=sqrt(n);
	for(int i=1;i<=n;i++) cin>>val[i];
	for(int i=1;i<=n;i++){
		bl[i] = (i-1)/blo + 1;
		ve[bl[i]].push_back(val[i]);
	}
	for(int i=1;i<=bl[n];i++){
		sort(ve[i].begin(),ve[i].end());
	}
	for(int i=1;i<=m;i++){
		int opt,l,r,d;
		cin>>opt>>l>>r>>d;
		if(opt==1) change(l,r,d);
		else printf("%d\n",query(l,r,d));
	}
 } 

set写法:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int val[N],n,m,blo;
int add[N],bl[N];
set<int>st[550];
void change(int l,int r,int d){
	for(int i=l;i<=min(r,bl[l]*blo);i++){
		st[bl[l]].erase(val[i]);
		val[i]+=d;
		st[bl[l]].insert(val[i]);
	}
	if(bl[l]!=bl[r]){
		for(int i=(bl[r]-1)*blo+1;i<=r;i++){
			st[bl[r]].erase(val[i]);
			val[i]+=d;
			st[bl[r]].erase(val[i]);
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++) add[i]+=d;
}
int query(int l,int r,int d){
	int ans = -1;
	for(int i=l;i<=min(r,bl[l]*blo);i++){
		if(val[i]+add[bl[i]]<d) ans = max(ans,val[i]+add[bl[i]]);
	}
	if(bl[l]!=bl[r]){
		for(int i=(bl[r]-1)*blo;i<=r;i++)
	    if(val[i]+add[bl[i]]<d) ans = max(ans,val[i]+add[bl[i]]);
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++){
		int x = d - add[i];
		set<int>::iterator it = st[i].lower_bound(x);
		if(it==st[i].begin()) continue;
		--it;
		ans = max(ans,*it+add[i]);
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	blo = sqrt(n);
	for(int i=1;i<=n;++i) cin>>val[i];
	for(int i=1;i<=n;++i){
		bl[i] = (i-1)/blo + 1;
		st[bl[i]].insert(val[i]);
	}
	for(int i=1;i<=m;i++){
		int opt,l,r,d;
		cin>>opt>>l>>r>>d;
		if(opt==1) change(l,r,d);
		else printf("%d\n",query(l,r,d));
	}
}

 

分块入门 4:

给出一个长为n的数列,以及m个操作,操作涉及区间加法,区间求和。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
int val[N],L[N],R[N],pos[N],sum[N],add[N];
int n,m;
inline void change(int l,int r,int d){
	int p = pos[l] ,q = pos[r];
	if(p==q) for(int i=l;i<=r;i++) val[i]+=d,sum[p]+=d;
	else {
		for(int i=l;i<=R[p];i++)  val[i]+=d,sum[p]+=d;
		for(int i=L[q];i<=r;i++) val[i]+=d,sum[q]+=d;
		for(int i=p+1;i<=q-1;i++) add[i]+=d;
	}
}
inline int query(int l,int r){
	int p = pos[l] , q = pos[r];
	int ans = 0;
	if(p==q) for(int i=l;i<=r;i++) ans += val[i]+add[i];
	else {
		for(int i=p+1;i<=q-1;i++) ans+=sum[i]+(R[i]-L[i]+1)*add[i];
		for(int i=l;i<=R[p];i++) ans += val[i]+add[p];
		for(int i=L[q];i<=r;i++) ans += val[i]+add[q];
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	int t = sqrt(n);
	for(int i=1;i<=n;i++) cin>>val[i];
	for(int i=1;i<=t;i++){
		L[i] = (i-1)*t+1;
		R[i] = i*t;
	}
	if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1;i<=t;i++){
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i;
			sum[i]+=val[j];
		}
	}
	for(int i=1;i<=m;i++){
		int opt,l,r,d;
		cin>>opt;
		if(opt==1) {
			cin>>l>>r>>d;
			change(l,r,d); 
		}
		else {
			cin>>l>>r;
		   printf("%d\n",query(l,r));
		}
	}
}

分块入门 5 :

给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。

对于区间开方,我们可以想到,对于一个区间的数,经过数次开方后,他们会变为0或1,所以采取一种分块优化的暴力做法,只要每个整块暴力开方后,记录一下元素是否都变成了 0 / 1,区间修改时跳过那些全为 0 / 1 的块即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
int n,m,blo;
int val[N],bl[N],sum[N],vis[N];
void solve_sqrt(int x){//暴力判断这一块是否变成了1 或 0 
	if(vis[x]) return;
	vis[x]=1;
	sum[x]=0;
	for(int i=(x-1)*blo+1;i<=x*blo;i++){
		val[i] = sqrt(val[i]);
		sum[x]+=val[i];
		if(val[i]>1) vis[x]=0;
	}
}
void change(int l,int r){
	for(int i=l;i<=min(r,bl[l]*blo);i++){
		sum[bl[l]]-=val[i];
		val[i] = sqrt(val[i]);
		sum[bl[l]]+=val[i];
	}
	if(bl[l]!=bl[r]){
		for(int i=(bl[r]-1)*blo+1;i<=r;i++){
			sum[bl[r]]-=val[i];
			val[i] = sqrt(val[i]);
			sum[bl[r]]+=val[i];
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	   solve_sqrt(i);
}
int query(int l,int r){
	int ans = 0;
	for(int i=l;i<=min(bl[l]*blo,r);i++) ans+=val[i];
	if(bl[l]!=bl[r])
	   for(int i=(bl[r]-1)*blo+1;i<=r;i++) ans+=val[i];
	for(int i=bl[l]+1;i<=bl[r]-1;i++) ans+=sum[i];
	return ans;
}
int main()
{
	cin>>n>>m;
	blo =sqrt(n); 
	for(int i=1;i<=n;i++) cin>>val[i];
	for(int i=1;i<=n;i++){
		bl[i]=(i-1)/blo + 1;
		sum[bl[i]]+=val[i];
	}
	for(int i=1;i<=m;i++){
		int opt,l,r;
		cin>>opt>>l>>r;
		if(opt==1) change(l,r);
		else printf("%d\n",query(l,r));
	}
}

 

分块入门6:

给出一个长为 n 的数列,以及 n 个操作,操作涉及单点插入,单点询问,数据随机生成。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int val[maxn],s[maxn];
int n,m,blo,q;
struct node{
	int s,t;
};
vector<int>e[1010];
inline node query(int x){//返回某一元素在块中的位置 
	int i=1;
	while(x>(int)e[i].size())
	   x-=(int)e[i].size(),i++;
	return node{i,x-1};
}

inline void rebuild(){//重新分块 
	int top=0;
	for(int i=1;i<=q;++i){
		for(int j=0;j<e[i].size();++j)
			s[++top]=e[i][j];
		e[i].clear();
	}
	blo=sqrt(top),q=(top-1)/blo+1;
	for(int i=1;i<=top;i++)
		e[(i-1)/blo+1].push_back(s[i]);
}

inline void change(int l,int r){
	node x = query(l);
	e[x.s].insert(e[x.s].begin()+x.t,r);
	if(e[x.s].size()>20*blo)//此块元素过多,重新分块 
		rebuild();
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i) 
		cin>>val[i];
	blo = sqrt(n);q=(n-1)/blo+1;
	for(int i=1;i<=n;i++) 
		e[(i-1)/blo+1].push_back(val[i]);//分块 
	for(int i=1;i<=m;i++){
		int opt,l,r;
		cin>>opt;
		if(opt==1){
			cin>>l>>r;
			change(l,r);
		}
		else {
			cin>>r;
			node x = query(r);
			cout<<e[x.s][x.t]<<endl;
		}
	}
}

 

分块入门 7

给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int val[maxn],bl[1000],add[1000],mul[1000],sum[1000];
int n,m,blo;
void resert(int x){
	for(int i=(bl[x]-1)*blo+1;i<=min(n,bl[x]*blo);i++)
	val[i] = val[i]*mul[x]+add[x];
	mul[x]=1,add[x]=0;
}
void change(int l,int r,int d,int opt){
	resert(bl[l]);
	for(int i=l;i<=min(r,bl[l]*blo);i++){
		if(opt==1) val[i]+=d;
		else val[i]*=d;
	}
	if(bl[l]!=bl[r]){
		resert(bl[r]);
		for(int i=(bl[r]-1)*blo+1;i<=r;i++){
			if(opt==1) val[i]+=d;
		    else val[i]*=d;
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++){
		if(opt==1) add[i]+=d;
		else {
			add[i]*=d;
			mul[i]*=d;
		}
	}
}
int main()
{
	for(int i=1;i<=1000;i++) mul[i]=1;
	cin>>n>>m;
	blo = sqrt(n);
	for(int i=1;i<=n;i++) {
		cin>>val[i];
		bl[i]=(i-1)/blo+1;
		sum[bl[i]]+=val[i];
	}
	for(int i=1;i<=m;i++){
		int opt,l,r,d;
		cin>>opt;
		if(opt==1||opt==2){//加法
		    cin>>l>>r>>d;
		    change(l,r,d,opt); 
		} 
		else {
			cin>>r;
			cout<<val[r]*mul[bl[r]]+add[bl[r]]<<endl;
		}
	}
 } 

 

分块入门 8

给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。

 

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
int val[N],vis[1000],bl[N];
int q,n,m,blo;
void reset(int x){
	if(vis[x]==-1) return;
	for(int i=(x-1)*blo+1;i<=blo*x;++i)
	val[i]=vis[x];
	vis[x]=-1;
}
int query(int l,int r,int d){
	int ans = 0;
    reset(bl[l]);
    for(int i=l;i<=min(r,bl[l]*blo);++i) {
		if(val[i]==d) ans++;
		val[i]=d;
	}
	if(bl[l]!=bl[r]){
		reset(bl[r]);
		for(int i=(bl[r]-1)*blo+1;i<=r;++i) {
			if(val[i]==d) ans++;
			val[i]=d;
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;++i){
		if(vis[i]!=-1){
			if(vis[i]!=d) vis[i]=d;
			else ans+=blo;
		}
		else {
			for(int j=(i-1)*blo+1;j<=i*blo;j++)
			   if(val[j]!=d) val[j]=d;
			   else ans++;
			vis[i]=d;
		}
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>val[i];
	for(int i=1;i<=1000;i++) vis[i]=-1;
	blo = sqrt(n);
	for(int i=1;i<=n;++i) bl[i] = (i-1)/blo+1;
	for(int i=1;i<=m;i++){
		int l,r,d;
		cin>>l>>r>>d;
		printf("%d\n",query(l,r,d));
	}
}

 

内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了多项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值