【暖*墟】 #数据结构# 分块入门训练题1~9

版权声明:https://blog.csdn.net/flora715 https://blog.csdn.net/flora715/article/details/83213029

引入 foreword

数列分块就是把数列中【每m个元素打包起来】,达到优化算法的目的。

把每m个元素分为一块,共有n/m块,区间修改涉及O(n/m)个整块,以及两侧两个不完整的块。

每次操作对每个整块直接标记,而由于不完整的块的元素比较少,暴力修改元素的值。

每次询问时返回元素的值加上其所在块的加法标记。每次操作的复杂度是O(n/m)+O(m)。

根据均值不等式,当m取√n时总复杂度最低,所以默认分块大小为√n,复杂度为O(√n)。

一. 分块的常用数组

int n,m; //总个数n,每块的大小m

int a[500019],tag[500019]; //原数组 和 标记数组(对于每一块)

int pos[500019]; //pos[i]=(i-1)/m+1,即记录i属于哪一块

还有一些在特定情况下使用的数组:

vector<int> v[519]; //记录每块的元素,并分别排序
//在每块中维护单调性,用lowerbound函数维护块的满足条件的值的个数

set<int> s[519]; //set记录每块的元素(已分别排序去重)

bool okk[500019]; //标记整个块内的元素是否全部满足某个条件

int b[N]; //b[i]用于数组离散化 或者 重新分块

int f[4021][4021]; //预处理出从第i块到第j块的总信息(比如区间众数)

int tag_add[500019],tag_mul[500019]; //多种标记...

二. 分块的常用函数

(1) void add(int l,int r,int x):用于区间的修改。在该函数中更新标记数组tag。

【add函数中一般包含三个步骤】a.处理l的边界块,暴力更新整块;

b.处理r的边界块,暴力更新整块;c.在中间的所有整块,打上标记。

(2) int query(int l,int r,int x):用于区间的查询。在该函数中整合所有修改值。

【query函数中一般包含三个步骤】a.处理l的边界块,暴力求值;

b.处理r的边界块,暴力求值;c.在中间的所有整块,整合所有标记,整体计算。

(3) void rebuild():重新分块。一般用于会添加元素的分块过程中。

【rebuild函数中一般包含三个步骤】a.记录a数组中的原状态到中转数组b中;

b.得到新的块大小,修改各个数组;c.将b数组的值完全赋给a数组。

(4) 数组的离散化+分块。一般用于数较大的时候,节约内存。

1.把a数组复制到b数组中; 2.b数组排序+去重;

3.将a数组中的数用【在b数组中的排名】表示出来;

4.完成离散化后,输出原值时,调用 b [ a [ i ] ]

sort(b+1,b+n+1); int n0=unique(b+1,b+n+1)-(b+1); //排序去重

for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n0+1,a[i])-b;
//↑↑寻找数值a[i]在离散化数组b中对应位置的下标(类似编号,排名)

(5) 其他函数。一般用于块间初始化问题,或者特殊的块间修改情况。

三. 分块练习题

      目录    【练习1】区间修改,单点查询 

【练习2】区间修改,查询比x小的个数

【练习3】区间修改,求区间内x的前驱

【练习4】区间修改,区间求和

【练习5】区间开方取整,区间求和

【练习6】单点插入,单点询问

【练习7】区间乘法,区间加法,单点查询

【练习8】区间询问,区间修改

【练习9】查询区间最小众数


【练习1】区间修改,单点查询

  • 每块标记tag,剩下的l、r两个边界块直接修改。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习1】区间修改,单点查询。*/

/*【分析】数列分块就是把数列中【每m个元素打包起来】,达到优化算法的目的。
如果把每m个元素分为一块,共有n/m块,区间加会涉及O(n/m)个整块,以及两侧两个不完整的块。
每次操作对每个整块直接标记,而由于不完整的块的元素比较少,暴力修改元素的值。
每次询问时返回元素的值加上其所在块的加法标记。每次操作的复杂度是O(n/m)+O(m)。
根据均值不等式,当m取√n时总复杂度最低,所以默认分块大小为√n,复杂度为O(√n)。*/

int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

int a[500019],tag[500019];

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(int l,int r,int x){ //l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x;
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x;
    for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x;
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n); m=sqrt(n);
    for(int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1,op,l,r,x;i<=n;i++){
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==0) adds(l,r,x);
        if(op==1) printf("%d\n",tag[pos[r]]+a[r]);
    } //↑↑op=1时,l、x输入但忽略
}

【练习2】区间修改,查询比x小的个数

  • vector数组记录每块的元素,排序维护块内的递增性。
  • 完整的块可以直接用lower_bound返回第一个大于等于x的位置。
  • 不完整的块直接暴力修改,但需要在每块内每次重新排序。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习2】区间修改,查询比x小的个数。*/

/*【分析】记录每块的元素,排序维护块内的递增性。
完整的块可以直接用lower_bound返回第一个大于等于x的位置。
不完整的块直接暴力修改,但需要在每块内每次重新排序。*/

int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

int a[500019],tag[500019]; 
//↑↑对整块使用tag标记,在询问的时候再计算tag的影响

vector<int> v[519]; //记录每块的元素,并分别排序

void changes(int num){
    v[num].clear(); //↓↓如果是最后一块,可能不完整
    for(int i=(num-1)*m+1;i<=min(num*m,n);i++)
        v[num].push_back(a[i]);
    sort(v[num].begin(),v[num].end());
}

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(int l,int r,int x){ //l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]+=x;
    changes(pos[l]); //将l块中的元素重新排序
    if(pos[l]!=pos[r]){ //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x;
        changes(pos[r]); //将r块中的元素重新排序
    } for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x;
}

int querys(int l,int r,int x){
    int anss=0; //↓↓l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) 
        if(a[i]+tag[pos[l]]<x) anss++; //直接枚举统计
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) 
            if(a[i]+tag[pos[r]]<x) anss++;
    for(int i=pos[l]+1;i<=pos[r]-1;i++)
        anss+=lower_bound(v[i].begin(),v[i].end(),x-tag[i])-v[i].begin();
    return anss; //↑↑lower_bound返回第一个大于等于x的位置,省略了位置+1和答案-1
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n); m=sqrt(n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/m+1;
        v[pos[i]].push_back(a[i]);
    } for(int i=1;i<=pos[n];i++)
        sort(v[i].begin(),v[i].end());
    for(int i=1,op,l,r,x;i<=n;i++){
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==0) adds(l,r,x); //题意中是查询比x^2小的个数
        if(op==1) printf("%d\n",querys(l,r,x*x));
    }
}

【练习3】区间修改,求区间内x的前驱

  • 题意是求严格小于x的元素、所以要去重,需要使用set。
  • 完整的块用lower_bound,不完整的块暴力修改。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习3】区间修改,求区间内比x小的最大元素。*/

/*【分析】题意的意思是严格小于、所以要去重,需要使用set。
完整的块用lower_bound,不完整的块暴力修改。*/

int n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

int a[500019],tag[500019]; 
//↑↑对整块使用tag标记,在询问的时候再计算tag的影响

set<int> s[519]; //set记录每块的元素(已分别排序去重)

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(int l,int r,int x){ //l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++)
        s[pos[i]].erase(a[i]),a[i]+=x,s[pos[i]].insert(a[i]);
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) 
            s[pos[i]].erase(a[i]),a[i]+=x,s[pos[i]].insert(a[i]);
    for(int i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x;
}

int querys(int l,int r,int x){
    int maxx=-1; //↓↓l,r同段 或者 先处理l的边界段
    for(int i=l;i<=min(r,pos[l]*m);i++) 
        if(a[i]+tag[pos[l]]<x) maxx=max(maxx,a[i]+tag[pos[l]]);
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(int i=(pos[r]-1)*m+1;i<=r;i++) 
            if(a[i]+tag[pos[r]]<x) maxx=max(maxx,a[i]+tag[pos[r]]);
    for(int i=pos[l]+1;i<=pos[r]-1;i++){
        set<int>::iterator it=s[i].lower_bound(x-tag[i]);
        if(it==s[i].begin()) continue; //没有比它小的
        it--; maxx=max(maxx,*it+tag[i]);
    } return maxx;
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n); m=sqrt(n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/m+1;
        s[pos[i]].insert(a[i]);
    } for(int i=1,op,l,r,x;i<=n;i++){
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==0) adds(l,r,x);
        if(op==1) printf("%d\n",querys(l,r,x));
    }
}

【练习4】区间修改,区间求和

  • 和单点查询类似,用sum数组记录每块的元素和即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习4】区间修改,区间求和。*/

/*【分析】和单点查询类似,用sum数组记录每块的元素和即可。*/

ll n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

ll a[500019],tag[500019],sum[500019]; 
//↑↑对整块使用tag标记,在询问的时候再计算tag的影响

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void adds(ll l,ll r,ll x){ //l,r同段 或者 先处理l的边界段
    for(ll i=l;i<=min(r,pos[l]*m);i++) a[i]+=x,sum[pos[l]]+=x;
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(ll i=(pos[r]-1)*m+1;i<=r;i++) a[i]+=x,sum[pos[r]]+=x;
    for(ll i=pos[l]+1;i<=pos[r]-1;i++) tag[i]+=x;
}

ll querys(ll l,ll r){
    ll anss=0; //↓↓l,r同段 或者 先处理l的边界段
    for(ll i=l;i<=min(r,pos[l]*m);i++) anss+=a[i]+tag[pos[l]];
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(ll i=(pos[r]-1)*m+1;i<=r;i++) anss+=a[i]+tag[pos[r]];
    for(ll i=pos[l]+1;i<=pos[r]-1;i++) anss+=sum[i]+tag[i]*m;
    return anss; //返回区间和
}

int main(/*hs_love_wjy*/){
    scanf("%lld",&n); m=sqrt(n);
    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),
        pos[i]=(i-1)/m+1,sum[pos[i]]+=a[i];
    for(ll i=1,op,l,r,x;i<=n;i++){
        scanf("%lld%lld%lld%lld",&op,&l,&r,&x);
        if(op==0) adds(l,r,x); //↓↓要求%(x+1)
        if(op==1) printf("%lld\n",querys(l,r)%(x+1));
    }
}

【练习5】区间开方取整,区间求和

  • 这个问题就有点玄学了...想不到就懵了...
  • 2^31开5次平方就得到1了,所以操作次数>5次之后区间内就都是1了。
  • 用okk[i]数组标记第i块内的数是不是全都小于等于1了;如果不是就整块更新。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习5】区间开方取整,区间求和。*/

/*【分析】这个问题就有点玄学了...想不到就懵了...
因为最大值是2^31,它开5次平方就得到1了,所以操作次数>5次之后区间内就都是1了。
用okk[i]数组标记第i块内的数是不是全都小于等于1了;如果不是就整块更新。*/

ll n,m,pos[500019]; //pos[i]=(i-1)/m+1,即i属于哪一块

ll a[500019],tag[500019],sum[500019]; //【此题不用tag】

bool okk[500019]; //标记块内是不是全都<=1

//↓↓每块标记tag,剩下的l、r两个边界块直接修改

void sqrt_(ll num){ //整段的开方
    if(okk[num]) return;
    okk[num]=true,sum[num]=0;
    for(ll i=(num-1)*m+1;i<=min(num*m,n);i++){
        a[i]=sqrt(a[i]),sum[num]+=a[i];
        if(a[i]>1) okk[num]=false;
    }
}

void adds(ll l,ll r){
    if(okk[pos[l]]==false){ //l,r同段 或者 先处理l的边界段
        for(ll i=l;i<=min(r,pos[l]*m);i++)
            sum[pos[l]]-=a[i],a[i]=sqrt(a[i]),sum[pos[l]]+=a[i];
        okk[pos[l]]=true; //判断l的边界段是否全部变为<=1
        for(ll i=(pos[l]-1)*m+1;i<=min(n,pos[l]*m);i++)
            if(a[i]>1){ okk[pos[l]]=false; break; }
    } if(pos[l]!=pos[r]&&okk[pos[r]]==false){ //同理,处理r的边界段
        for(ll i=(pos[r]-1)*m+1;i<=r;i++)
            sum[pos[r]]-=a[i],a[i]=sqrt(a[i]),sum[pos[r]]+=a[i];
        okk[pos[r]]=true; //判断r的边界段是否全部变为<=1
        for(ll i=(pos[r]-1)*m+1;i<=min(n,pos[r]*m);i++)
            if(a[i]>1){ okk[pos[r]]=false; break; }
    } for(ll i=pos[l]+1;i<=pos[r]-1;i++) sqrt_(i);
}

ll querys(ll l,ll r){
    ll anss=0; //↓↓l,r同段 或者 先处理l的边界段
    for(ll i=l;i<=min(r,pos[l]*m);i++) anss+=a[i];
    if(pos[l]!=pos[r]) //同理,处理r的边界段
        for(ll i=(pos[r]-1)*m+1;i<=r;i++) anss+=a[i];
    for(ll i=pos[l]+1;i<=pos[r]-1;i++) anss+=sum[i];
    return anss; //返回区间和
}

int main(/*hs_love_wjy*/){
    memset(okk,false,sizeof(okk));
    scanf("%lld",&n); m=sqrt(n);
    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),
        pos[i]=(i-1)/m+1,sum[pos[i]]+=a[i];
    for(ll i=1,op,l,r,x;i<=n;i++){
        scanf("%lld%lld%lld%lld",&op,&l,&r,&x);
        if(op==0) adds(l,r); //不要怀疑,x就是没有用的
        if(op==1) printf("%lld\n",querys(l,r));
    }
}

【练习6】单点插入,单点询问

  • 用a[i][j]记录第i块的第j个数,len[i]记录第i块有多少个数。
  • 暴力找到插入的点应在的块,暴力把此块中在此位置后面所有数的id往后挪。
  • 所以当某个块的太大时、就重新分块。此时先用b数组分好块,在复制到a中。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习6】单点插入,单点询问。*/

/*【分析】用a[i][j]记录第i块的第j个数,len[i]记录第i块有多少个数。
暴力找到插入的点应在的块,暴力把此块中在此位置后面所有数的id往后挪。
所以当某个块的太大时、就重新分块。此时先用b数组分好块,在复制到a中。*/

int a[1019][1019],b[1019][1019],len[1019]; //b:a的转存数组;len:每一块的长度 
int n,N,add_num=0,m; //add_num:新增点数,m:新总长,N:每块的大小 

void rebuild(){ //重新分块
    int now=0,k=(m-1)/N+1,lens=0; 
    m+=N; add_num=0; N=sqrt(m); //新加了N个元素
    for(int i=1;i<=k;i++){ //原块数
        for(int j=1;j<=len[i];j++){ //每个数
            now++; int kk=(now-1)/N+1; 
            b[kk][++lens]=a[i][j]; a[i][j]=0; 
            if(lens==N) lens=0;
        } 
    } k=(m-1)/N+1; //新的总块数
    for(int i=1;i<k;i++){ //前方的所有满的块数
        len[i]=N; for(int j=1;j<=len[i];j++) a[i][j]=b[i][j]; 
    } if(m%N==0) len[k]=N; else len[k]=m-N*N; 
    for(int j=1;j<=len[k];j++) a[k][j]=b[k][j]; //复制
} 

int main() { 
    scanf("%d",&n);N=sqrt(n); m=n; 
    for(int i=1;i<=n;i++){ int s,k=(i-1)/N+1; 
        scanf("%d",&s),a[k][++len[k]]=s; //直接存到分块数组中 
    } for(int i=1,op,l,r,x;i<=n;i++){ 
        scanf("%d%d%d%d",&op,&l,&r,&x); 
        if(op==0){ add_num++; int now=0,t; 
            for(t=1;t<=N;t++){ //修改l位置的数
                if(now+len[t]>=l) break; //找到所在块的编号
                else now+=len[t]; //在后面的块中
            } len[t]++; l=l-now; //第t块的len++,寻找在块中的具体位置
            for(int i=len[t];i>l;i--) a[t][i]=a[t][i-1]; //后方数字后移 
            a[t][l]=r; if(add_num==N) rebuild(); //如果增加总数达到N,重新分块 
        } else{ int t,now=0; 
            for(t=1;t<=N;t++){ //查询r位置的数
                if(now+len[t]>=r) break; //找到所在块的编号
                else now+=len[t]; //在后面的块中
            } printf("%d\n",a[t][r-now]); //直接输出
        } 
    } 
}

【练习7】区间乘法,区间加法,单点查询

  • 维护乘法标记tag_mul和加法标记tag_add。
  • 乘法可以看做是:a*tag_mul+0;加法可以看做是:a*1+tag_add。
  • 每次新加入一个操作:加法时,直接将整块的tag_add[i]+x;
  • 乘法时,将整快的tag_mul[i]*x,tag_add[i]*x。
  • 对于左右的零散块,将块中所有值都计算出来,清空标记。
  • 计算总的标记(单点查询时)就是:a*tag_mul+tag_add。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习7】区间乘法,区间加法,单点查询。*/

/*【分析】维护乘法标记tag_mul和加法标记tag_add。
乘法可以看做是:a*tag_mul+0;加法可以看做是:a*1+tag_add。
每次新加入一个操作:加法时,直接将整块的tag_add[i]+x;
乘法时,将整快的tag_mul[i]*x,tag_add[i]*x。
对于左右的零散块,将每块的所有值都计算出来,清空标记,暴力修改。
计算总的标记就是:a*tag_mul+tag_add。*/

const int mod=10007;

int n,m,tol,T,a[500019],pos[500019];

int tag_add[500019],tag_mul[500019];

void add(int l,int r,int x){
    for(int i=(pos[l]-1)*m+1;i<=min(n,pos[l]*m);i++)
        a[i]=(a[i]*tag_mul[pos[i]]%mod+tag_add[pos[i]])%mod;
    tag_add[pos[l]]=0,tag_mul[pos[l]]=1; //↑↑计算这一块先前的所有更新
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]=(a[i]+x)%mod; //更新a[i]
    if(pos[l]==pos[r]) return;
    for(int i=(pos[r]-1)*m+1;i<=min(n,pos[r]*m);i++)
        a[i]=(a[i]*tag_mul[pos[i]]%mod+tag_add[pos[i]])%mod;
    tag_add[pos[r]]=0,tag_mul[pos[r]]=1; //↑↑计算这一块先前的所有更新
    for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]=(a[i]+x)%mod;
    for(int i=pos[l]+1;i<=pos[r]-1;i++) tag_add[i]=(tag_add[i]+x)%mod;
}

void mul(int l,int r,int x){
    for(int i=(pos[l]-1)*m+1;i<=min(n,pos[l]*m);i++)
        a[i]=(a[i]*tag_mul[pos[i]]%mod+tag_add[pos[i]])%mod;
    tag_add[pos[l]]=0,tag_mul[pos[l]]=1; //↑↑计算这一块先前的所有更新
    for(int i=l;i<=min(r,pos[l]*m);i++) a[i]=(a[i]*x)%mod; //更新a[i]
    if(pos[l]==pos[r]) return;
    for(int i=(pos[r]-1)*m+1;i<=min(n,pos[r]*m);i++)
        a[i]=(a[i]*tag_mul[pos[i]]%mod+tag_add[pos[i]])%mod;
    tag_add[pos[r]]=0,tag_mul[pos[r]]=1; //↑↑计算这一块先前的所有更新
    for(int i=(pos[r]-1)*m+1;i<=r;i++) a[i]=(a[i]*x)%mod;
    for(int i=pos[l]+1;i<=pos[r]-1;i++) //注意:mul和add标记都要*x
        tag_mul[i]=(tag_mul[i]*x)%mod,tag_add[i]=(tag_add[i]*x)%mod;
}

int main(){
    scanf("%d",&n); m=sqrt(n);
    for(int i=1;i<=n;i++) //预处理pos,初始化tag_mul
        scanf("%d",&a[i]),pos[i]=(i-1)/m+1,tag_mul[i]=1;
    for(int i=1,op,l,r,x;i<=n;i++){
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==0) add(l,r,x%mod); //加法
        if(op==1) mul(l,r,x%mod); //乘法
        if(op==2) printf("%d\n",(a[r]*tag_mul[pos[r]]%mod+tag_add[pos[r]]%mod)%mod);
    } //查询r点现在的值
}

【练习8】区间询问,区间修改

  • 题意:查询区间内有多少个x,并把区间内全部改成x。
  • 类似开方的那个题。用okk数组记录是否块内每个数字都相同。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习8】区间询问,区间修改。*/

//题意:查询区间内有多少个x,并把区间内全部改成x。

/*【分析】类似开方的那个题。用okk数组记录是否块内每个数字都相同。*/

const int maxn=500019;

int n,m,a[maxn],pos[maxn];

int add[maxn]; bool okk[maxn];

int L(int num){ return (num-1)*m+1; }

int R(int num){ return min(n,num*m); }

int query(int l,int r,int x){
    int ans=0; //查找在区间l,r内有多少个x
    if(okk[pos[l]]){ if(add[pos[l]]==x) ans+=min(R(pos[l]),r)-l+1; }
    else{ for(int i=l;i<=min(R(pos[l]),r);i++) ans+=(a[i]==x); }
    if(pos[l]==pos[r])    return ans;
    if(okk[pos[r]]){ if(add[pos[r]]==x) ans+=r-L(pos[r])+1;} 
    else{ for(int i=L(pos[r]);i<=r;i++) ans+=(a[i]==x); }
    for(int i=pos[l]+1;i<=pos[r]-1;i++){
        if(okk[i]){ if(add[i]==x) ans+=R(i)-L(i)+1; } 
        else{ for(int j=L(i);j<=R(i);j++) ans+=(a[j]==x); }
    } return ans;
}

void change(int l,int r,int x){
    if(okk[pos[l]]){ okk[pos[l]]=false;
        for(int i=L(pos[l]);i<=R(pos[l]);i++) a[i]=add[pos[i]];   
    } for(int i=l;i<=min(r,R(pos[l]));i++) a[i]=x;
    if(pos[l]==pos[r]) return;
    if(okk[pos[r]]){ okk[pos[r]]=false;
        for(int i=L(pos[r]);i<=R(pos[r]);i++) a[i]=add[pos[i]];
    } for(int i=L(pos[r]);i<=r;i++) a[i]=x;
    for(int i=pos[l]+1;i<=pos[r]-1;i++) add[i]=x,okk[i]=true;
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n),m=sqrt(n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),pos[i]=(i-1)/m+1;
    for(int i=1,l,r,x;i<=n;i++){
        scanf("%d%d%d",&l,&r,&x);
        printf("%d\n",query(l,r,x)),change(l,r,x);
    }
}

【练习9】查询区间最小众数

  • 分块,预处理出从第i块到第j块的众数,用f[i][j]记录。
  • 用vector来保存【每个数】的所有的出现位置,
  • 求不完整的块上的每个数的出现次数,找出现次数最多的数。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【分块入门练习9】查询区间最小众数。*/

/*【分析】分块,预处理出从第i块到第j块的众数,用f[i][j]记录。
用vector来保存【每个数】的所有的出现位置,对于不完整的块,
求每个数的出现次数,找最大的出现次数。*/

const int N=100019;

int n,m,a[N],pos[N],f[4021][4021],b[N];

int cnt[100019]; vector<int> v[N];

int L(int num){ return (num-1)*m+1; }

int R(int num){ return min(n,num*m); }

void init(int num){
    int now_=0,ans=0;
    memset(cnt,0,sizeof(cnt));
    for(int i=L(num);i<=n;i++){
        cnt[a[i]]++; if(cnt[a[i]]>ans){
            now_=a[i],ans=cnt[a[i]];
        } if(cnt[a[i]]==ans&&a[i]<now_){
            now_=a[i],ans=cnt[a[i]]; //区间最小众数
        } f[num][pos[i]]=now_; //每次都更新
    } //↑↑预处理:从第i块到第j块(整块)的最小众数
}

int ask_(int l,int r,int x){ //v数组从前到后保存每个数字出现的[位置]
    return upper_bound(v[x].begin(),v[x].end(),r)
            -lower_bound(v[x].begin(),v[x].end(),l);
} //↑↑查找从位置l到位置r,数字x一共出现了多少次

int query(int l,int r){
    int now=f[pos[l]+1][pos[r]-1],ans=ask_(l,r,now),ans_;
    for(int i=l;i<=min(r,R(pos[l]));i++){ 
        ans_=ask_(l,r,a[i]);
        if(ans_>ans){ ans=ans_; now=a[i]; }
        if(ans_==ans&&a[i]<now){ ans=ans_; now=a[i]; }
    } if(pos[l]==pos[r]) return now;
    for(int i=L(pos[r]);i<=r;i++){ 
        ans_=ask_(l,r,a[i]);
        if(ans_>ans){ ans=ans_; now=a[i]; }
        if(ans_==ans&&a[i]<now){ ans=ans_; now=a[i]; }
    } return now; //返回众数
}

int main(/*hs_love_wjy*/){
    scanf("%d",&n),m=50; //据说50可以快很多...
    for(int i=1;i<=n;i++) //↑↑反正sqrt(n)会tle...
        scanf("%d",&a[i]),pos[i]=(i-1)/m+1,b[i]=a[i];
    sort(b+1,b+n+1); int n0=unique(b+1,b+n+1)-(b+1);
    for(int i=1;i<=n;i++){ //↑↑将包含的所有元素排序并去重(离散化)
        a[i]=lower_bound(b+1,b+n0+1,a[i])-b; //【离散化】
        //↑↑第一个>=a[i]的数,就是b中存放的数字a[i]。只需要-b即可返回在b数组中的下标
        v[a[i]].push_back(i); //把每个【数】出现的【位置】放入vector数组中
    } for(int i=1;i<=pos[n];i++) init(i);
    for(int i=1,l,r;i<=n;i++) //询问区间(i,j)的最小众数
        scanf("%d%d",&l,&r),printf("%d\n",b[query(l,r)]);
}

                                         ——时间划过风的轨迹,那个少年,还在等你

猜你喜欢

转载自blog.csdn.net/flora715/article/details/83213029
今日推荐