• 【算法学习笔记】分块——优雅的暴力


    “这个世界本是没有分块的,小数据的题多了,便有了分块。” ——沃镃基硕德

    \(0.\) 简介

    分开是一种毒瘤优秀的数据结构,它的基本思想就是对一个数列分为几个小“块”,对于查询中的整块可以直接一扫而过,剩下的七零八碎的东西直接暴力。

    很显然,我们会发现分的块越小,数量就越多,维护整块的信息就变的趋近于零块。而相反,块越大,维护块内信息就变得越慢,所以我们需要找一个折中的大小。

    我们设数列长度为\(n\),块的大小为\(s\),块的个数为\(c\)

    假设我们对全局进行处理,则时间复杂度应为\(O(c+s)\)

    我们考虑如何使时间开销最小,这里运用的数学知识为均值不等式:

    \[\dfrac{a+b}{2}\leq \sqrt{2}{ab}\ \ (\text{当且仅当}a=b\text{时,等号成立}) \]

    由于\(s*c=n\),所以\(\min{(s+c)}=\sqrt{2}n,\ s=c=\sqrt{2}n\)

    \(1.\) 基本分块

    分块1

    \(loj6277.\)数列分块入门 \(1\)

    人话翻译:区间修改,单点查询。

    本题是分块入门题。具体思想和线段树差不多,都是为某一个区间打上一个标记,并对整块进行标记修改,零块直接修改。和线段树不同的是分块的标记不用下放(无相交区间)。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define rg register
    #define int long long
    #define ull unsigned long long
    
    namespace Enterprise{
    	inline int read(){
    		rg int s=0,f=0;
    		rg char ch=getchar();
    		while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
    		return f?-s:s;
    	}
    	
    	const int N=5e4+15;
    	int a[N],n,tag[255],size,pos[N];
    	
    	
    	inline void main(){
    		
    		n=read();
    		size=(int)sqrt(n);
    		
    		for(rg int i=1;i<=n;i++){
    			pos[i]=(i-1)/size+1;
    			a[i]=read();
    		}
    		
    		for(rg int i=1;i<=n;i++){
    			int opt=read(),l=read(),r=read(),c=read();
    			if(!opt){
    				rg int lx=pos[l],rx=pos[r];
    				for(rg int j=l;pos[j]==lx&&j<=r;j++) a[j]+=c;
    				if(lx==rx) continue;
    				for(rg int j=r;pos[j]==rx;j--) a[j]+=c;
    				for(rg int j=lx+1;j<rx;j++) tag[j]+=c;
    			}else{
    				printf("%lld\n",a[r]+tag[pos[r]]);
    			}
    		}
    	}
    }
    
    signed main(){
    	Enterprise::main();
    	return 0;
    }
    

    分块2

    \(loj6278.\) 数列分块入门 \(2\)

    人话翻译:区间修改,统计区间严格小于某个数的数的个数。

    本题要注意的是每次\(update\)零块必须重新sort,而对于整块修改,由于统一加了一个相同的数,不用重新sort。

    这里的\(ac\)代码用到了一个小技巧:利用每块下标为\(0\)的位置来存储本块中所含元素的个数。

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstdlib>
    #include<cctype>
    
    
    using namespace std;
    
    #define rg register
    #define int long long
    #define ull unsigned long long
    
    namespace Enterprise{
    	inline int read(){
    		rg int s=0,f=0;
    		rg char ch=getchar();
    		while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
    		return f?-s:s;
    	}
    
    	const int N=5e5+15;
    
    	int a[N],tag[500],pos[N],n,size,b[500][500],sum;
    
    	#define s(x) ((x-1)*size+1)
    	#define e(x) (x*size)
    
    	inline void _sort(int x){
    //		memset(b[x],0,sizeof(b[x]));
    		b[x][0]=0;
    		for(rg int i=s(x);i<=min(n,e(x));i++) b[x][++b[x][0]]=a[i];
    		sort(b[x]+1,b[x]+b[x][0]+1);
    	}
    
    	inline void update(int l,int r,int v){
    		rg int lx=pos[l],rx=pos[r];
    		for(rg int i=l;pos[i]==lx&&i<=r;i++) a[i]+=v;
    		_sort(lx);
    		if(lx==rx) return;
    		for(rg int i=r;pos[i]==rx;i--) a[i]+=v;
    		_sort(rx);
    		for(rg int i=lx+1;i<rx;i++) tag[i]+=v;
    	}
    
    	inline int query(int l,int r,int v){
    		rg int ans=0,lx=pos[l],rx=pos[r];
    		for(rg int i=l;pos[i]==lx&&i<=r;i++){
    			if(a[i]+tag[lx]<v) ans++;
    		}
    		if(lx==rx) return ans;
    		for(rg int i=r;pos[i]==rx;i--){
    			if(a[i]+tag[rx]<v) ans++;
    		}
    		for(rg int i=lx+1;i<rx;i++){
    			rg int tmp=0,ll=1,rr=b[i][0];
    			while(ll<=rr){
    				rg int mid=(ll+rr)>>1;
    				if(b[i][mid]+tag[i]>=v) rr=mid-1;
    				else ll=mid+1,tmp=mid;
    			}
    			ans+=tmp;
    		}
    		return ans;
    	}
    
    	inline void main(){
    		n=read();
    		size=(int)sqrt(n);
    		sum=(n/size)+(n%size>0);
    	
    		for(rg int i=1;i<=n;i++){
    //			a[i]=read();
    			pos[i]=(i-1)/size+1;
    //			id[i]=(i-1)%size+1;
    			b[pos[i]][++b[pos[i]][0]]=a[i]=read();
    		}
    	
    		for(rg int i=1;i<=sum;i++) sort(b[i]+1,b[i]+b[i][0]+1);
    	
    		for(rg int i=1;i<=n;i++){
    			rg int opt=read(),l=read(),r=read(),v=read();https://loj.ac/problem/6279
    			if(!opt) update(l,r,v);
    			else printf("%lld\n",query(l,r,v*v));
    		}		
    	}
    }
    
    signed main(){
    	Enterprise::main();
    	return 0;
    }
    

    分块3

    \(loj6279.\) 数列分块入门 \(3\)

    人话翻译:区间修改,区间查找前驱。

    • 前驱是指严格小于某个数的数中的最大值,即查询 \(\max\limits_{l\leq i\leq r} a_i (a_i<k)\)

    根据前驱定义可得此题块内二分即可完成

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstdlib>
    #include<cctype>
    
    
    using namespace std;
    
    #define rg register
    #define int long long
    #define ull unsigned long long
    
    namespace Enterprise{
    	inline int read(){
    		rg int s=0,f=0;
    		rg char ch=getchar();
    		while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
    		return f?-s:s;
    	}
    
    	const int N=1e6+15;
    
    	int a[N],tag[500],pos[N],n,size,b[500][500],sum;
    
    	#define s(x) ((x-1)*size+1)
    	#define e(x) (x*size)
    
    	inline void _sort(int x){
    //		memset(b[x],0,sizeof(b[x]));
    		b[x][0]=0;
    		for(rg int i=s(x);i<=min(n,e(x));i++) b[x][++b[x][0]]=a[i];
    		sort(b[x]+1,b[x]+b[x][0]+1);
    	}
    
    	inline void update(int l,int r,int v){
    		rg int lx=pos[l],rx=pos[r];
    		for(rg int i=l;pos[i]==lx&&i<=r;i++) a[i]+=v;
    		_sort(lx);
    		if(lx==rx) return;
    		for(rg int i=r;pos[i]==rx;i--) a[i]+=v;
    		_sort(rx);
    		for(rg int i=lx+1;i<rx;i++) tag[i]+=v;
    	}
    
    	inline int query(int l,int r,int v){
    		rg int ans=-1,lx=pos[l],rx=pos[r];
    		for(rg int i=l;pos[i]==lx&&i<=r;i++){
    			if(a[i]+tag[lx]<v) ans=max(ans,a[i]+tag[lx]);
    		}
    		if(lx==rx) return ans;
    		for(rg int i=r;pos[i]==rx;i--){
    			if(a[i]+tag[rx]<v) ans=max(ans,a[i]+tag[rx]);
    		}
    		for(rg int i=lx+1;i<rx;i++){
    			rg int ll=1,rr=b[i][0];
    			while(ll<=rr){
    				rg int mid=(ll+rr)>>1;
    				if(b[i][mid]+tag[i]<v) ans=max(ans,b[i][mid]+tag[i]),ll=mid+1;
    				else rr=mid-1;
    			}
    		}
    		return ans;
    	}
    
    	inline void main(){
    		n=read();
    		size=(int)sqrt(n);
    		sum=(n/size)+(n%size>0);
    	
    		for(rg int i=1;i<=n;i++){
    //			a[i]=read();
    			pos[i]=(i-1)/size+1;
    //			id[i]=(i-1)%size+1;
    			b[pos[i]][++b[pos[i]][0]]=a[i]=read();
    		}
    	
    		for(rg int i=1;i<=sum;i++) sort(b[i]+1,b[i]+b[i][0]+1);
    	
    		for(rg int i=1;i<=n;i++){
    			rg int opt=read(),l=read(),r=read(),v=read();
    			if(!opt) update(l,r,v);
    			else{
    				rg int out=query(l,r,v);
    				printf("%lld\n",out);
    			}
    			
    		}		
    	}
    }
    
    signed main(){
    	Enterprise::main();
    	return 0;
    }
    

    分块4

    \(loj6280.\) 数列分块入门 \(4\)

    人话翻译:区间修改,区间查询

    只需开一个整块存和的数组即可。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define rg register
    #define int long long
    #define ull unsigned long long
    
    namespace Enterprise{
    	inline int read(){
    		rg int s=0,f=0;
    		rg char ch=getchar();
    		while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
    		return f?-s:s;
    	}
    	
    	const int N=5e4+15;
    	int a[N],pos[N],tag[500],sum[500],n,size;
    		
    	inline void update(int l,int r,int v){
    		rg int lx=pos[l],rx=pos[r];
    		for(rg int i=l;pos[i]==lx&&i<=r;i++){
    			a[i]+=v;
    			sum[lx]+=v;
    		}
    		if(lx==rx) return;
    		for(rg int i=r;pos[i]==rx;i--){
    			a[i]+=v;
    			sum[rx]+=v;
    		}
    		for(rg int i=lx+1;i<rx;i++){
    			sum[i]+=v*size;
    			tag[i]+=v;
    		}
    	}
    	
    	inline int query(int l,int r){
    		rg int lx=pos[l],rx=pos[r];
    		rg int ans=0;
    		for(rg int i=l;pos[i]==lx&&i<=r;i++) ans+=a[i]+tag[lx];
    		if(lx==rx) return ans;
    		for(rg int i=r;pos[i]==rx;i--) ans+=a[i]+tag[rx];
    		for(rg int i=lx+1;i<rx;i++) ans+=sum[i];
    		return ans;
    	}	
    			
    	inline void main(){
    		n=read();
    		size=sqrt(n);
    		for(rg int i=1;i<=n;i++){
    			pos[i]=(i-1)/size+1;
    			sum[pos[i]]+=a[i]=read();
    		}
    		
    		for(rg int i=1;i<=n;i++){
    			rg int opt=read(),l=read(),r=read(),v=read();
    			if(!opt) update(l,r,v);
    			else printf("%lld\n",query(l,r)%(v+1));
    		}			
    	}
    }
    
    signed main(){
    	Enterprise::main();
    	return 0;
    }
    

    分块5

    \(6281.\) 数列分块入门 \(5\)

    在此链接三倍经验题与其题解

    \(SP2713\ GSS4\ -\ Can\ you\ answer\ these\ queries\ IV\)

    \(luogu\ P4145\) 上帝造题的七分钟2 / 花神游历各国

    题解

    由于本题数据范围为\(1e5\),每个数\(\leq 2^{31}-1\) 所以可知,最多开4次就可以变为1。很显然,对1进行开平方是没有意义的。所以我们只需对整块加入一个最大标记。

    线段树明显可过此题,所以我就用线段树过了此题

    $To\ boldly\ go\ where\ no\ one\ has\ gone\ before$
  • 相关阅读:
    Gin+Gorm小项目
    python实现监控信息收集
    Vue引入Stylus
    Go搭建一个Web服务器
    saltstack高效运维
    04-01 Django之模板层
    03-01 Django之视图层
    02-01 Django之路由层
    HTTP协议
    01-01 Web应用
  • 原文地址:https://www.cnblogs.com/UssEnterprise/p/12088705.html
Copyright © 2020-2023  润新知