zoukankan      html  css  js  c++  java
  • [知识学习] 分块

    贴几个板子,供自己看


    分块核心:暴力操作

    把一串序列分成(sqrt{n})个块,对每个块操作

    左端不完整块,右端不完整块暴力计算

    中间完整块lazy标记


    最简单的分块题:

    数列分块入门1

    #include <bits/stdc++.h>
    #define N (100000+5)
    using namespace std;
    int n,k,a[N],lazy[N],pos[N],L[N],R[N];//a原数组,lazy懒标记,pos[i]:i对应的块,L[i]:块i的左端点,R[i]:块i的右端点
    void add(int l,int r,int c){//区间加
    	for(int i=l;i<=min(R[pos[l]],r);i++){//左端不完整区间处理
    		a[i]+=c;
    	}
    	if(pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++){//右端不完整区间处理
    			a[i]+=c;
    		}
    	}
    	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;//中间完整区间处理
    }
    int main(){
    	scanf("%d",&n);
    	k=sqrt(n);//每k个数分1块
    	int sum=n/k+(n%k!=0);//共分sum个块
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1;//每个数在哪个块
    	for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;//左右端点
    	R[sum]=n;
    	for(int i=1;i<=n;i++){
    		int opt,l,r,c;
    		scanf("%d%d%d%d",&opt,&l,&r,&c);
    		if(opt==0) add(l,r,c);
    		else if(opt==1) printf("%d
    ",a[r]+lazy[pos[r]]);
    	}
    	return 0;
    }
    
    

    支持区间加,查找区间内前驱

    可以利用C++STL里的set维护每个块,查lower_bound

    每次修改时先把块里的删掉,修改后在加进来

    数列分块入门3

    #include <bits/stdc++.h>
    #define N (100000+5)
    using namespace std;
    int n,k,a[N],lazy[N],pos[N],L[N],R[N];
    set<int> t[N];
    void add(int l,int r,int c){//同上
    	for(int i=l;i<=min(R[pos[l]],r);i++){
    		t[pos[l]].erase(a[i]);//先删掉原始的
    		a[i]+=c;//更改
    		t[pos[l]].insert(a[i]);//再插入新的
    	}
    	if(pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++){
    			t[pos[r]].erase(a[i]);
    			a[i]+=c;
    			t[pos[r]].insert(a[i]);
    		}
    	}
    	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;
    }
    int pre(int l,int r,int c){//查询前驱,用set维护
    	int ans=-1;
    	for(int i=l;i<=min(R[pos[l]],r);i++)//统计左端不完整区间
    		if(a[i]+lazy[pos[l]]<c) ans=max(ans,a[i]+lazy[pos[l]]);
    	if(pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++)//统计右端不完整区间
    			if(a[i]+lazy[pos[r]]<c) ans=max(ans,a[i]+lazy[pos[r]]);
    	}
    	for(int i=pos[l]+1;i<pos[r];i++){//用STL找中间的最大值
    		int k=c-lazy[i];
    		set<int>::iterator it=t[i].lower_bound(k);
    		if(it==t[i].begin()) continue;
    		it--;
    		ans=max(ans,*it+lazy[i]);
    	}
    	return ans;
    }
    int main(){
    	scanf("%d",&n);
    	k=sqrt(n);
    	int sum=n/k+(n%k!=0);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1,t[pos[i]].insert(a[i]);
    	for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;
    	R[sum]=n;
    	for(int i=1;i<=n;i++){
    		int opt,l,r,c;
    		scanf("%d%d%d%d",&opt,&l,&r,&c);
    		if(opt==0) add(l,r,c);
    		else if(opt==1) printf("%d
    ",pre(l,r,c));
    	}
    	return 0;
    }
    

    支持区间加,区间求和

    思路大同小异,这里不细说,依然是老套路

    数列分块入门4

    #include <bits/stdc++.h>
    #define N (100000+5)
    #define int long long//开long long
    using namespace std;
    int n,k,b,a[N],pos[N],lazy[N],sum[N],L[N],R[N];
    void add(int l,int r,int c){//区间加操作,不解释了
    	for(int i=l;i<=min(R[pos[l]],r);i++) a[i]+=c,sum[pos[l]]+=c;
    	if(pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++) a[i]+=c,sum[pos[r]]+=c;
    	}
    	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c,sum[i]+=c*k;
    }
    int getsum(int l,int r,int c){//区间求和操作,一样的道理
    	int ans=0;
    	for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]+lazy[pos[l]],ans%=(c+1);
    	if(pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++) ans+=a[i]+lazy[pos[r]],ans%=(c+1);
    	} 
    	for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i],ans%=(c+1);
    	return ans%(c+1);
    }
    signed main(){
    	scanf("%lld",&n);
    	k=sqrt(n);
    	b=(n/k)+(n%k!=0);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		pos[i]=(i-1)/k+1;
    		sum[pos[i]]+=a[i];
    	}
    	for(int i=1;i<=b;i++){
    		L[i]=(i-1)*k+1,R[i]=i*k;
    	}
    	R[b]=n;
    	for(int i=1;i<=n;i++){
    		int opt,l,r,c;
    		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
    		if(opt==0) add(l,r,c);
    		else if(opt==1) printf("%lld
    ",getsum(l,r,c));
    	}
    	return 0;
    }
    

    支持区间开方,区间求和

    开方:最终得到的数都会是0或1(向下取整了)

    所以分块维护,当哪个块里的数全部都是0或1是,再怎么开方都不会变了

    所以用vis数组记录一下那些块已经只有0或1,就可以大大节约复杂度

    数列分块入门5

    #include <bits/stdc++.h>
    #define N (1000000+5)
    #define int long long
    using namespace std;
    int n,bl,k,a[N],sum[N],pos[N],L[N],R[N];
    bool vis[N];
    void solve(int x){//解决完整块开方的函数
    	if(vis[x]) return;//这个块全部都是0或1,直接跳出不用开方
    	vis[x]=1;
    	for(int i=L[x];i<=R[x];i++){
    		sum[x]-=a[i];//减去
    		a[i]=sqrt(a[i]);//开方
    		sum[x]+=a[i];//再加上
    		if(a[i]>1) vis[x]=0;
    	}
    }
    void add(int l,int r,int c){//不完整块开方函数,同理
    	if(!vis[pos[l]]){
    		for(int i=l;i<=min(R[pos[l]],r);i++){
    			sum[pos[l]]-=a[i];
    			a[i]=sqrt(a[i]);
    			sum[pos[l]]+=a[i];
    		}
    		vis[pos[l]]=1;
    		for(int i=L[pos[l]];i<=R[pos[l]];i++){
    			if(a[i]>1){
    				vis[pos[l]]=0;
    				break;
    			}
    		}
    	}
    	if(!vis[pos[r]]&&pos[l]!=pos[r]){
    		for(int i=L[pos[r]];i<=r;i++){
    			sum[pos[r]]-=a[i];
    			a[i]=sqrt(a[i]);
    			sum[pos[r]]+=a[i];
    		}
    		vis[pos[r]]=1;
    		for(int i=L[pos[r]];i<=R[pos[r]];i++){
    			if(a[i]>1){
    				vis[pos[r]]=0;
    				break;
    			}
    		}
    	}
    	for(int i=pos[l]+1;i<pos[r];i++) solve(i);
    }
    int getsum(int l,int r,int c){
    	int ans=0;
    	for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]; 
    	if(pos[l]!=pos[r])for(int i=L[pos[r]];i<=r;i++) ans+=a[i];
    	for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i];
    	return ans;
    }
    signed main(){
    	scanf("%lld",&n);
    	k=sqrt(n),bl=(n/k)+(n%k!=0);//同样
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		pos[i]=(i-1)/k+1;
    		sum[pos[i]]+=a[i];
    	}
    	for(int i=1;i<=bl;i++){
    		L[i]=(i-1)*k+1;
    		R[i]=i*k;
    	}
    	R[bl]=n;
    	for(int i=1;i<=n;i++){
    		int opt,l,r,c;
    		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
    		if(opt==0) add(l,r,c);
    		else if(opt==1) printf("%lld
    ",getsum(l,r,c));
    	}
    	return 0;
    }
    

    就到这里吧

    转载请注明出处--Xx_queue
  • 相关阅读:
    C#的多态性
    冒泡法,改进冒泡法排序和反序算法
    [收藏]D3D渲染流程简介
    [收藏]字符串表达式求值
    Windows下使用VC++编译GNU科学计算库 GNU Scientific Library(GSL)
    VC++ 高精度定时timeSetEvent和回调成员方法
    [收藏]C/C++数组名与指针区别深层探索
    线性表操作
    线性表综合运用
    两个线性表融合的算法
  • 原文地址:https://www.cnblogs.com/Xx-queue/p/12193193.html
Copyright © 2011-2022 走看看