zoukankan      html  css  js  c++  java
  • 学习笔记--分块

    • 前言

      分块是一种拓展性比较强的数据结构,对于一些难以合并的区间信息,线段树处理起来比较棘手(如区间众数),但是分块以其灵活的特点能够较快直观地处理

      本次笔记主要以hzwer的九道分块练习题与博客为主

      hzwer介绍分块的博客:http://hzwer.com/8053.html

      hzwer的分块练习题:https://loj.ac/problems/search?keyword=分块

    • 区间加法&区间求和

      应该算最为基础的一种问题模型了,用其他数据结构当然能很快地解决,不过我们用这个让大家知道分块的原理.

      首先我们要知道"分块",顾名思义,就是把一些信息分成一块一块来处理,于是对于一个区间上的信息修改或查询,这个区间可能既覆盖了一些整块,左右两边又还有一些多出来的部分,或者区间比较小被一个整块给覆盖。

      这就给我们处理的思路,对于被区间覆盖的整块,直接对这个整块的信息进行操作,两边多出来零散的部分呢就直接暴力处理。当然还有种情况就是如果区间被一个整块覆盖,也直接对区间暴力处理。这样的话设序列大小为(N),询问次数为(Q),块的大小为(sqrt N),时间复杂度就为(O((N+Q)sqrt N))

      代码方面个人认为lyd的更简洁易懂,但实际上与hzwer的本质是相同的

      (lydrainbowcat)

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <cmath>
    #include <vector>
    #include <map>
    #include <queue>
    #define ll long long 
    #define ri register int 
    using namespace std;
    const int maxn=50005;
    const int inf=0xfffffff;
    template <class T>inline void read(T &x){
    	x=0;int ne=0;char c;
    	while(!isdigit(c=getchar()))ne=c=='-';
    	x=c-48;
    	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    	x=ne?-x:x;
    	return ;
    }
    int n,size; 
    int L[maxn],R[maxn],pos[maxn]; //L,R-每个块的左右端点;pos-元素所在块的编号 
    ll sum[maxn],tag[maxn],a[maxn];//sum-块的和 tag块的标记 a-元素(序列) 注意开long long  
    inline void add(int l,int r,int c){
    	int p=pos[l],q=pos[r]; 
    	if(p==q){
    		for(ri i=l;i<=r;i++){
    			a[i]+=c;
    		}
    		sum[p]+=(r-l+1)*c;
    	}
    	else{
    		for(ri i=p+1;i<=q-1;i++){
    			tag[i]+=c;
    		}
    		for(ri i=l;i<=R[p];i++)a[i]+=c;
    		sum[p]+=(R[p]-l+1)*c;
    		for(ri i=L[q];i<=r;i++)a[i]+=c;
    		sum[q]+=(r-L[q]+1)*c;
    	}
    }
    inline ll query(int l,int r){
    	int p=pos[l],q=pos[r];
    	ll ans=0;
    	if(p==q){
    		for(ri i=l;i<=r;i++)ans+=a[i];
    		ans+=tag[p]*(r-l+1);
    	}
    	else{
    		for(ri i=p+1;i<=q-1;i++)ans+=sum[i]+tag[i]*(R[i]-L[i]+1);
    		for(ri i=l;i<=R[p];i++)ans+=a[i]+tag[p];
    		for(ri i=L[q];i<=r;i++)ans+=a[i]+tag[q];
    	}
    	return ans;
    }
    int main(){
    	read(n);size=sqrt(n);
    	for(ri i=1;i<=n;i++){
    		read(a[i]);
    	}
    	for(ri i=1;i<=size;i++){
    		L[i]=(i-1)*size+1;   //标记每个块的左右端点位置 
    		R[i]=i*size;
    	}
    	if(R[size]<n){size++;L[size]=R[size-1]+1,R[size]=n;}//如果没凑齐就加一个块 
    	for(ri i=1;i<=size;i++){
    		for(ri j=L[i];j<=R[i];j++){
    			pos[j]=i;
    			sum[i]+=a[j];
    		}
    	}
    	int op,l,r,c;
    	for(ri i=1;i<=n;i++){
    		read(op),read(l),read(r),read(c);
    		if(!op){
    			add(l,r,c);
    		}
    		else printf("%d
    ",query(l,r)%(c+1));
    	}
    	return 0;
    }
    

    (hzwer)

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <cmath>
    #include <vector>
    #include <map>
    #include <queue>
    #define ll long long 
    #define ri register int 
    using namespace std;
    const int maxn=50005;
    const int inf=0xfffffff;
    template <class T>inline void read(T &x){
    	x=0;int ne=0;char c;
    	while(!isdigit(c=getchar()))ne=c=='-';
    	x=c-48;
    	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    	x=ne?-x:x;
    	return ;
    }
    int n; 
    int blo[maxn],block;
    ll sum[maxn],a[maxn],tag[maxn];//类似定义 
    inline void add(int l,int r,int c){
    	for(ri i=l;i<=min(blo[l]*block,r);i++){//处理最左边的块,blo[l]*block其实就是l块的右端点 
    		a[i]+=c;
    		sum[blo[i]]+=c;
    	}
    	if(blo[l]!=blo[r]){//如果 左右端点不在同一个块上 
    		for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){//处理最右块,(blo[r]-1)*block+1其实就是r块的左端点 
    			a[i]+=c;
    			sum[blo[i]]+=c;
    		}
    	}
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){//处理整块 
    		sum[i]+=block*c;
    		tag[i]+=c;
    	}
    }
    inline ll query(int l,int r,int p){
    	ll ans=0;
    	for(ri i=l;i<=min(blo[l]*block,r);i++){
    		ans+=a[i]+tag[blo[i]];
    	}
    	if(blo[l]!=blo[r]){
    		for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
    			ans+=a[i]+tag[blo[i]];
    		}
    	}
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){
    		ans+=sum[i];
    	}
    	return ans;
    }
    int main(){
    	read(n);
    	block=sqrt(n);
    	for(ri i=1;i<=n;i++){
    		read(a[i]);
    		blo[i]=(i-1)/block+1;
    		sum[blo[i]]+=a[i];
    	}
    	int op,l,r,c;
    	for(ri i=1;i<=n;i++){
    		read(op),read(l),read(r),read(c);
    		if(!op)add(l,r,c);
    		else printf("%d
    ",query(l,r,c+1)%(c+1));
    	}
    	return 0;
    }
    
    • 区间加法&区间小于某数个数

      这个思路比较有意思,我们在整块中二分,这就要求整块是有序的,但左右两边散块一但暴力求改后所处的块可能就会不有序,所以要重构那两个块。用vector可以大大减少代码量,但是千万要注意tag即标记对你操作的影响,对于初学者来说非常容易出错

      比较有趣的是暴力好象比分块更快

    #pragma GCC optimize(3) 
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <cctype>
    #include <vector>
    #include <map>
    #include <queue>
    #define ri register int 
    #define ll long long 
    using namespace std;
    const int inf=0xfffffff;
    const int maxn=50005;
    int a[maxn],blo[maxn],tag[maxn],block,c;
    vector <int> g[maxn];
    int n;
    template <class T>inline void read(T &x){
    	x=0;int ne=0;char c;
    	while(!isdigit(c=getchar()))ne=c=='-';
    	x=c-48;
    	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    	x=ne?-x:x;
    	return ;
    }
    inline void reset_block(int now){//重构
    	g[now].clear();
    	for(ri i=(now-1)*block+1;i<=min(now*block,n);i++){
    		g[now].push_back(a[i]);
    	}
    	sort(g[now].begin(),g[now].end());
    }
    inline void add(int l,int r){
    	for(ri i=l;i<=min(blo[l]*block,r);i++){
    		a[i]+=c;
    	}
    	reset_block(blo[l]);
    	if(blo[l]!=blo[r]){
    		for(ri i=(blo[r]-1)*block+1;i<=r;i++){
    			a[i]+=c;
    		}
    		reset_block(blo[r]);
    	}
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){
    		tag[i]+=c;
    	}
    }
    inline int query(int l,int r,int x){
    	int cnt=0;
    	for(ri i=l;i<=min(blo[l]*block,r);i++){
    		if(a[i]+tag[blo[i]]<x)cnt++;
    	}
    	if(blo[l]!=blo[r]){
    		for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
    			if(a[i]+tag[blo[i]]<x)cnt++;
    		}
    	}
    	int tmp;
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){
    		tmp=lower_bound(g[i].begin(),g[i].end(),x-tag[i])-g[i].begin();
    		cnt+=tmp;
    	}
    	return cnt;
    }
    int main(){
    	read(n);
    	block=sqrt(n);
    	for(ri i=1;i<=n;i++){
    		read(a[i]);
    		blo[i]=(i-1)/block+1;
    		g[blo[i]].push_back(a[i]);
    	}
    	for(ri i=1;i<=blo[n];i++){
    		sort(g[i].begin(),g[i].end());
    	}
    	int op,l,r;
    	for(ri i=1;i<=n;i++){
    		read(op),read(l),read(r),read(c);
    		if(!op){
    			add(l,r);
    		}
    		else{
    			printf("%d
    ",query(l,r,c*c));
    		}
    	}
    	return 0;
    }
    
    • 区间加法&区间前驱

      (X)的前驱就是小于(X)的最大数,用上面那题类似的思路,整块中二分,左右散块修改后重构,查询时暴力查询

      然而hzwer大佬用了set,可是我已经对set的常数产生了心理阴影(相对其他STL),同时LOJ讨论区中有人说set可以被hack,感兴趣的可以看一看set写法,这里给出的还是vector二分解法,同时注意tag对操作的影响

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <cmath>
    #include <vector>
    #include <map>
    #include <queue>
    #define ri register int 
    #define ll long long 
    using namespace std;
    const int inf=0xfffffff;
    const int maxn=100005;
    template <class T>inline void read(T &x){
    	x=0;int ne=0;char c;
    	while(!isdigit(c=getchar()))ne=c=='-';
    	x=c-48;
    	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    	x=ne?-x:x;
    	return ;
    }
    int n;
    int blo[maxn],block,tag[maxn],a[maxn];
    vector <int>g[maxn];
    inline void reset_block(int now){
    	g[now].clear();
    	for(ri i=(now-1)*block+1;i<=min(now*block,n);i++){
    		g[now].push_back(a[i]);
    	}
    	sort(g[now].begin(),g[now].end());
    }
    inline void add(int l,int r,int c){
    	for(ri i=l;i<=min(blo[l]*block,r);i++){
    		a[i]+=c;
    	}
    	reset_block(blo[l]);
    	if(blo[l]!=blo[r]){
    		for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
    			a[i]+=c;
    		}
    		reset_block(blo[r]);
    	}
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){
    		tag[i]+=c;
    	}
    }
    inline int query(int l,int r,int x){
    	int ans=-inf;
    	for(ri i=l;i<=min(blo[l]*block,r);i++){
    		if(a[i]+tag[blo[i]]<x)ans=max(a[i]+tag[blo[i]],ans);
    	}
    	if(blo[l]!=blo[r]){
    		for(ri i=(blo[r]-1)*block+1;i<=min(r,n);i++){
    			if(a[i]+tag[blo[i]]<x)ans=max(a[i]+tag[blo[i]],ans);
    		}
    	}
    	int tmp=0;
    	for(ri i=blo[l]+1;i<=blo[r]-1;i++){
    		tmp=lower_bound(g[i].begin(),g[i].end(),x-tag[i])-g[i].begin();
    		if(tmp!=0)ans=max(ans,g[i][tmp-1]+tag[i]);//注意这里要加上标记 
    	}
    	if(ans==-inf)ans=-1;
    	return ans;
    }
    int main(){
    	read(n);
    	block=sqrt(n);
    	for(ri i=1;i<=n;i++){
    		read(a[i]);
    		blo[i]=(i-1)/block+1;
    		g[blo[i]].push_back(a[i]);
    	}
    	for(ri i=1;i<=blo[n];i++){
    		sort(g[i].begin(),g[i].end());
    	}
    	int op,l,r,c;
    	for(ri i=1;i<=n;i++){
    		read(op),read(l),read(r),read(c);
    		if(!op){
    			add(l,r,c);
    		}
    		else{
    			printf("%d
    ",query(l,r,c));
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Java学习笔记(一)
    大端模式和小端模式
    C语言数据的表示和存储(IEEE 754标准)
    C语言的limits.h文件
    有关计算机系统的一些东西
    基于51单片机设计的简易电子琴
    11G新特性 -- ASM Fast Mirror Resync
    11G新特性 -- variable size extents
    11G新特性 -- ASM的兼容性
    log file switch (checkpoint incomplete)
  • 原文地址:https://www.cnblogs.com/Rye-Catcher/p/9194001.html
Copyright © 2011-2022 走看看