首页 > 其他分享 >块状数组

块状数组

时间:2022-11-14 18:00:56浏览次数:44  
标签:re int bl st 块状 数组 区间 inline

基于分块思想,常用的有数列分块,之间分块,以及值域分块

把一个数组分为几个块,块内信息整体保存,两边散块进行暴力

首先进行分块

	int len=sqrt(n);/*选择块长,也可以手动调节*/
	for(int i=1;i<=n;i++)bl[i]=(i-1)/len+1;/*每个点所属块的编号*/
	for(int i=1;i<=bl[n];i++){
		st[i]=ed[i-1]+1;/*每个块的起始点*/
		ed[i]=st[i]+len-1;/*每个块的终止点*/
	}
	ed[bl[n]]=n;/*最后长度不够len的块的终止点是n*/

同时可以用一些数组来初始化整个块内的信息,如果只有询问的话还可以有连续块的前缀和

	for(int i=1;i<=n;i++)sum[bl[i]]+=a[i];

对于区间修改而言,若两个端点在一个块内,直接暴力修改,否则暴力修改两边散块,维护中间整块的信息及懒惰标记,若果修改是独立的,即可以单独为散块修改而不需要其他散块和整块的信息,那么可以稍微递归

inline void update(int l,int r,int x){
    if(bl[l]==bl[r]){
        for(int i=l;i<=r;i++)a[i]+=x,sum[bl[i]]+=x;/*暴力修改并维护块内的权值和*/
        return;
    }
    update(l,ed[bl[l]],x),update(st[bl[r]],r,x);/*更新左右散块*/
    for(int i=bl[l]+1;i<bl[r];i++)la[i]+=x/*整个块内的懒惰标记*/,sum[i]+=(ed[i]-st[i]+1)*x;/*维护整个块内的权值和*/
}

对于区间查询而言,若两端点在一个块内,直接暴力查询,否则查询两边散块,中间成块,若所查询的信息具有独立性,那么可以稍微递归

inline int ask(int l,int r){
    int re=0;
    if(bl[l]==bl[r]){
        for(int i=l;i<=r;i++)re+=a[i]+la[bl[i]];/*注意标记下方*/
        return re;
    }
    re+=ask(l,ed[bl[l]])+ask(st[bl[r]],r);/*左右散块查询*/
    for(int i=bl[l]+1;i<bl[r];i++)re+=sum[i];
    return re;
}

查询区间和和区间开方。a[i]=1时开方无意义,每个数最多开方6次,维护区间和和一个标记,标记区间内是否每个元素都为1,全为1的块跳过即可

#include<bits/stdc++.h>

using namespace std;

#define int long long
const int N=1e5+5;
int n,m,a[N],sum[N],la[N],bl[N],st[N],ed[N],len;
inline char gc(){static char buf[1<<22],*s=buf,*t=buf;return s==t&&(t=(s=buf)+fread(buf,1,1<<22,stdin),s==t)?EOF:*s++;}template<class T>inline T read(T&x){char c=gc(),f=x=0;while(c<'0'||c>'9')c=gc();while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc();return x=f?-x:x;}inline void read(string&s){char c=gc();s="";while(c=='\n'||c==' ')c=gc();while(c!='\n'&&c!=' ')s+=c,c=gc();}template<class T,class...Args>inline void read(T&x,Args&...args){read(x),read(args...);}char buf[1<<22],*s=buf;inline void pc(char c){if(s-buf==1<<22)fwrite(buf,1,1<<22,stdout),s=buf;*s++=c;}template<class T>inline void put(T x){if(x<0)pc('-'),x=-x;if(x>9)put(x/10);pc(x%10|48);}
inline void update(int l,int r){
    if(bl[l]==bl[r]){
        if(la[bl[l]])return;/*整个区间都是1,无需开方直接跳过*/
        for(int i=l;i<=r;i++)sum[bl[i]]-=a[i]/*维护区间和*/,a[i]=sqrt(a[i])/*进行开方*/,sum[bl[i]]+=a[i];/*修复区间和*/
        if(sum[bl[l]]==ed[bl[l]]-st[bl[l]]+1/*由于不可能有<=0的数,所以区间和等于区间长度时及全等于1*/)la[bl[l]]=1;
        return;
    }
    update(l,ed[bl[l]]);
    update(st[bl[r]],r);
    for(int i=bl[l]+1;i<bl[r];i++)update(st[i],ed[i]);
}
inline int ask(int l,int r){
    int re=0;
    if(bl[l]==bl[r]){
        for(int i=l;i<=r;i++)re+=a[i];
        return re;
    }
    re+=ask(l,ed[bl[l]])+ask(st[bl[r]],r);
    for(int i=bl[l]+1;i<bl[r];i++)re+=sum[i];
    return re;
}
signed main() {
	read(n);
    len=400;
    for(int i=1;i<=n;i++)bl[i]=(i-1)/len+1,read(a[i]),sum[bl[i]]+=a[i];
    for(int i=1;i<=bl[n];i++){
        st[i]=ed[i-1]+1;
        ed[i]=st[i]+len-1;
        if(ed[i]>n)ed[i]=n;
    }
    read(m);
    while(m--){
        int op,l,r;
        read(op,l,r);
        if(l>r)swap(l,r);
        if(op)put(ask(l,r)),pc('\n');
        else update(l,r);
    }
    fwrite(buf,1,s-buf,stdout);
	return 0;
}

区间平方和区间开方,输出区间和。将区间开方视为区间加,维护每个数被平方的次数,若>1则打区间减的标记,若区间最大值=1则跳过开方操作,最后欧拉定力预处理2的k次方幂统计答案

#include<bits/stdc++.h>

using namespace std;

#define int long long
const int N=2e5+5,mod=998244353;
int n,m,len,a[N],bl[N],st[N],ed[N],la[N],cnt[N],mi[N],sum[N],p2[N],ans;
inline int po(int x,int k,int mod,int re=1){
    for(;k;k>>=1,x=x*x%mod)(k&1)&&(re=re*x%mod);
    return re;
}
inline void up(int x){
    mi[x]=2e9;
    for(int i=st[x];i<=ed[x];i++)mi[x]=min(mi[x],cnt[i]+la[x]);/*维护块内平方次数的最小值*/
}
inline void down(int x){
    for(int i=st[x];i<=ed[x];i++)cnt[i]+=la[x];/*标记下放*/
    la[x]=0;
}
inline void update(int l,int r){
    if(bl[l]==bl[r]){
        down(bl[l]);
        for(int i=l;i<=r;i++){
            if(a[i]==1)continue;
            if(cnt[i])cnt[i]--;/*平方次数-1,相当于开方一次*/
            else{
                a[i]=sqrt(a[i]);
                sum[bl[i]]+=a[i]==1;/*统计块内=1的数的个数*/
            }
        }
        up(bl[l]);
        return;
    }
    update(l,ed[bl[l]]);
    update(st[bl[r]],r);
    for(int i=bl[l]+1;i<bl[r];i++){
        if(sum[i]==ed[i]-st[i]+1)continue;/*无需再开方*/
        if(mi[i])mi[i]--,la[i]--;/*直接修改整块平方的标记*/
        else update(st[i],ed[i]);
    }
}
inline void add(int l,int r){
    if(bl[l]==bl[r]){
        down(bl[l]);
        for(int i=l;i<=r;i++)cnt[i]++;/*平方次数+1*/
        up(bl[l]);
        return;
    }
    add(l,ed[bl[l]]);
    add(st[bl[r]],r);
    for(int i=bl[l]+1;i<bl[r];i++)mi[i]++,la[i]++;
}
signed main(){
    scanf("%lld%lld",&n,&m);
    len=sqrt(n);
    for(int i=1;i<=n;i++)bl[i]=(i-1)/len+1,scanf("%lld",a+i);
    for(int i=1;i<=bl[n];i++){
        st[i]=ed[i-1]+1;
        ed[i]=st[i]+len-1;
    }
    ed[bl[n]]=n;
    p2[0]=1;
    for(int i=1;i<=m;i++)p2[i]=(p2[i-1]<<1)%(mod-1);
    while(m--){
        int op,l,r;
        scanf("%lld%lld%lld",&op,&l,&r);
        if(op==1)update(l,r);
        else add(l,r);
    }
    for(int i=1;i<=bl[n];i++)down(i);
    for(int i=1;i<=n;i++){
        if(a[i]==1)ans++;
        else ans+=po(a[i],p2[cnt[i]],mod);
        ans%=mod;
    }
    printf("%lld",ans%mod);
    return 0;
}

区间加和查询a[x]在过去多少秒时间内不小于y,初始0秒,第i个操作在第i秒。在时间轴上分块,对每个数单独处理,将询问离线并排序,对于t时刻给[l,r]+x,分成两部分,在处理第l个数时将时刻[t,m]+x,在处理第r+1个数时将时刻[t,m]-x来抵消影响

#include<bits/stdc++.h>

using namespace std;

#define int long long
const int N=1e5+5;
int n,m,a[N],ans[N],c[N],la[N],bl[N],st[N],ed[N],tot,len;
vector<int>o[N];
struct query{int op,t,x,v;bool operator<(const query&a)const{return x==a.x?t<a.t:x<a.x;}}q[N<<1];
inline void update(int l,int r,int x){
    if(bl[l]==bl[r]){
        o[bl[l]].clear();
        for(int i=l;i<=r;i++)c[i]+=x;/*暴力修改*/
        for(int i=st[bl[l]];i<=ed[bl[l]];i++)o[bl[i]].push_back(c[i]);/*维护块内信息*/
        sort(o[bl[l]].begin(),o[bl[l]].end());/*调整*/
        return;
    }
    update(l,ed[bl[l]],x);update(st[bl[r]],r,x);
    for(int i=bl[l]+1;i<bl[r];i++)la[i]+=x;/*懒惰标记*/
}
inline int ask(int l,int r,int x){
    int re=0;
    if(bl[l]==bl[r]){
        for(int i=l;i<=r;i++)re+=(c[i]+la[bl[i]]>=x);/*注意懒惰标记不下放*/
        return re;
    }
    re+=ask(l,ed[bl[l]],x)+ask(st[bl[r]],r,x);
    for(int i=bl[l]+1;i<bl[r];i++)re+=o[i].size()-(lower_bound(o[i].begin(),o[i].end(),x-la[i])-o[i].begin());/*没个整块内>=x的数的个数*/
    return re;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++){
        int op;ans[i]=-1;
        cin>>op;
        if(op==1){
            int l,r,x;
            cin>>l>>r>>x;
            q[++tot]={1,i,l,x};
            q[++tot]={1,i,r+1,-x};
        }
        else{
            int x,y;
            cin>>x>>y;
            q[++tot]={2,i,x,y};
        }
    }
    sort(q+1,q+1+tot);
    len=sqrt(++m);
    for(int i=1;i<=m;i++)bl[i]=(i-1)/len+1,o[bl[i]].push_back(0);
    for(int i=1;i<=bl[m];i++){
        st[i]=ed[i-1]+1;
        ed[i]=st[i]+len-1;
    }
    ed[bl[m]]=m;
    for(int i=1;i<=tot;i++){
        if(q[i-1].x!=q[i].x)update(1,m,a[q[i].x]-a[q[i-1].x]);/*新处理一个数,所有时刻全部更新*/
        if(q[i].op==1)update(q[i].t+1,m,q[i].v);/*在这和之后的时刻中进行更新*/
        else ans[q[i].t]=ask(1,q[i].t,q[i].v);/*查询初始到不包括这一秒*/
    }
    for(int i=1;i<m;i++)if(ans[i]!=-1)cout<<ans[i]<<'\n';
    return 0;
}

给定汉子种类c,每次询问[l,r]内有多少个数出现次数为正偶数次。

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+5;
int bl[N],st[N],ed[N],len,a[N],la,n,m,c,cnt[1010][N],ans[1010][1010],t[N];
inline int ask(int l,int r){
    int re=0;
    if(bl[l]+1>=bl[r]){/*处于相邻的块也是可以直接算的*/
        for(int i=l;i<=r;i++){
            t[a[i]]++;
            if(!(t[a[i]]&1))re++;
            else if(t[a[i]]>2)re--;
        }
        for(int i=l;i<=r;i++)t[a[i]]--;
        return re;
    }
    re=ans[bl[l]+1][bl[r]-1];/*暂时的答案*/
    for(int i=l;i<=ed[bl[l]];i++){
        t[a[i]]++;/*左边散块的桶*/
        int tmp=cnt[bl[r]-1][a[i]]-cnt[bl[l]][a[i]];/*前缀和查分求出当前颜色在整块中出现的次数*/
        if(!((t[a[i]]+tmp)&1))re++;/*随时维护*/
        else if(t[a[i]]+tmp>2)re--;
    }
    for(int i=st[bl[r]];i<=r;i++){
        t[a[i]]++;/*右边散块的桶*/
        int tmp=cnt[bl[r]-1][a[i]]-cnt[bl[l]][a[i]];
        if(!((t[a[i]]+tmp)&1))re++;
        else if(t[a[i]]+tmp>2)re--;
    }
    for(int i=l;i<=ed[bl[l]];i++)t[a[i]]--;/*桶复原*/
    for(int i=st[bl[r]];i<=r;i++)t[a[i]]--;
    return re;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>c>>m;
    len=sqrt(n);
    for(int i=1;i<=n;i++)bl[i]=(i-1)/len+1;
    for(int i=1;i<=bl[n];i++){
        st[i]=ed[i-1]+1;
        ed[i]=st[i]+len-1;
    }
    ed[bl[n]]=n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        cnt[bl[i]][a[i]]++;/*块bl[i]内颜色a[i]出现的次数*/
    }
    for(int i=1;i<=bl[n];i++)for(int j=0;j<=c;j++)cnt[i][j]+=cnt[i-1][j];/*处理成颜色j出现次数的前缀和*/
    for(int i=1;i<=bl[n];i++){
        for(int j=i;j<=bl[n];j++){
            ans[i][j]=ans[i][j-1];/*处理块i到块j中出现次数为正偶数次的数的个数*/
            for(int k=st[j];k<=ed[j];k++){
                t[a[k]]++;/*当前颜色的桶*/
                if(!(t[a[k]]&1))ans[i][j]++;/*随时维护*/
                else if(t[a[k]]>2)ans[i][j]--;/*变成奇数就-1*/
            }
        }
        memset(t,0,sizeof(t));
    }
    while(m--){
        int l,r;
        cin>>l>>r;
        l=(l+la)%n+1,r=(r+la)%n+1;
        if(l>r)swap(l,r);
        cout<<(la=ask(l,r))<<'\n';
    }
    return 0;
}

支持两种操作,区间循环右移一位,查询区间内x的出现次数。用deque维护,再开个桶统计每个块中各颜色的出现次数。

inline void modify(int l,int r){
    if(bl[l]==bl[r]){
        int x=q[bl[l]][r-st[bl[l]]];
        q[bl[l]].erase(q[bl[l]].begin()+(r-st[bl[l]]));
        q[bl[l]].insert(q[bl[l]].begin()+(l-st[bl[l]]),x);
        return;
    }
    int x=q[bl[r]][r-st[bl[r]]];
    q[bl[l]].insert(q[bl[l]].begin()+(l-st[bl[l]]),x);
    cnt[bl[l]][x]++;
    cnt[bl[r]][x]--;
    q[bl[r]].erase(q[bl[r]].begin()+(r-st[bl[r]]));
    for(int i=bl[l]+1;i<=bl[r];i++){
        int x=q[i-1].back();
        q[i].push_front(x);
        cnt[i][x]++;
        q[i-1].pop_back();
        cnt[i-1][x]--;
    }
}
inline int query(int l,int r,int k){
    int re=0;
    if(bl[l]==bl[r]){
        for(int i=l;i<=r;i++)re+=q[bl[l]][i-st[bl[l]]]==k;
        return re;
    }
    re+=query(l,ed[bl[l]],k)+query(st[bl[r]],r,k);
    for(int i=bl[l]+1;i<bl[r];i++)re+=cnt[i][k];
    return re;
}

标签:re,int,bl,st,块状,数组,区间,inline
From: https://www.cnblogs.com/safeng/p/16889833.html

相关文章

  • 805. 数组的均值分割
    805.数组的均值分割给定你一个整数数组 nums我们要将 nums 数组中的每个元素移动到 A 数组或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A)==a......
  • 数组操作索引得结果
    publicclassDemo1{publicstaticvoidmain(String[]args){Scannerscanner=newScanner(System.in);System.out.println("学生人数:");......
  • 数组:输入学生成绩;判断最值,成绩和
    publicclassDemo1{publicstaticvoidmain(String[]args){Scannerinput=newScanner(System.in);/*System.out.println("输入学生人数:")......
  • 319场周赛 最小公倍数为K的子数组的数目
    #319场周赛最小公倍数为K的子数组的数目给你一个整数数组nums和一个整数k,请你统计并返回nums的子数组中满足元素最小公倍数为k的子数组数目。子数组是数组......
  • 删除数组里的a
    publicstaticvoidmain(String[]args){//删除数组里面的所有a//数组元素都是连贯的//数组空间是不变的String[]strArray={"a","b","a","a","d"......
  • [LeetCode] 805. 数组的均值分割
    设变量sum:数组的和,n:数组元素个数,tot:子数组和,cnt:子数组元素个数通过简单的公式变换可以得到:sum/n=tot/cntsum/n的值是确定的,所以也就是需要找到一个子数......
  • 计算机等级考试二级C语言程序设计专项训练题——数组元素的删除
        数组元素a[i]的删除操作是使元素个数为n的数组(a[0],a[1],…,a[i-1],a[i],a[i+1],…,a[n-1])变成元素个数为n-1的数组(a[0],a[1],…,a[i-1],a[i+1],…,a[n-1]),由于数组在存储时......
  • 计算机等级考试二级C语言程序设计专项训练题——数组元素的移动
        在计算机等级考试二级C语言程序设计试题中,按要求对数组元素进行移动处理是一个重要的考点,有关数组元素移动的试题在历年考试试卷的程序填空题和程序设计题中经......
  • C语言数组越界和内存分配
    事情经过11月3日晚,今天遇到了一个神奇的现象,一个大小为10的数组可以容纳200个数据,直接震惊我了!今天发11月2日的参考代码,有一个同学给我看他的代码,大概是这样的intmain(......
  • 【剑指Offer学习】【面试题3 :二维数组中的查找】
    思路:规律从右上角开始,或左下开始。不能从左上角开始找,这样每次比较后向右和向下都是越来越大。publicclassP03_FindMaxInMatrix{/*规律从右上角开始:......