【数列分块入门题集+题解】

**总述:**数列分块在我的理解中,它能完成区间的许多看似复杂的操作,而树状数组和线段树对于有些操作也只能望而却步,在这里不做过多解释,每种题型的大体代码模块差不多,不过每个题有每个题的特点,有些细节和优化操作还是很妙的,这是一项比较巨大的工程,如有需要请看目录啦。

入门1:区间加法,单点查询

传送门
题意:给定n个数,完成n次操作,操作之一是将位于[l,r]的之间的数字都加c,之二是询问a[r] 的值(l和c忽略)
树状数组
解题思路:这里就不做过多解释了,毕竟是数列分块的主场~~~
Code:

#include<bits/stdc++.h>
#include<vector>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
typedef pair<int,int> PII;
map<string,int>mp;
PII q[100000];
int n,m;
int a[maxn]={0},b[maxn],c[maxn];
int lowbit(int x){
    return x&(-x);
}
void update(int i,int k){
    while(i<=n){
        c[i]+=k;
        i+=lowbit(i);
    }
}
int getsum(int i){
    int res=0;
    while(i>0){
        res+=c[i];
        i-=lowbit(i);
    }
    return res;
}
 
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        update(i,a[i]-a[i-1]);
    }
    for(int i=1;i<=n;i++)
    {
        int opt,l,r,c1;
        scanf("%d%d%d%d",&opt,&l,&r,&c1);
        if(opt==0)
        {
            update(l,c1);
            update(r+1,-c1);
        }
        else{
            printf("%d\n",getsum(r));
        }
    }
    return 0;
}

线段树Code:
解题思路:先解释一下概念,完整块:长度为n/m的块,不完整快:长度小于block的块,一般就是最后一个块;对于一个完整块区间同时加一个值的话,我们可以用一个懒惰标记记录一下,不完整快的话就只能暴力更新了,假设每个块的数量是n/m,暴力更新的复杂度最高是O(n/m),懒惰标记的复杂度是O(m),总的复杂度就是O(n/m+m),根据均值不等式,当每个块的数量是sqrt(n)时,复杂度最小,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];///原数组
int belong[maxn]; ///第i个元素属于哪一块
int lazy[maxn];///每块额外的懒惰标记
int sum[maxn];///每块的和
vector<int> v[1010];
void build(){   ///初始化
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;   ///每块的左边界
        r[i]=i*block;      ///每块的右边界
    }
    r[num]=n;   ///由于最后一块可能是不完整块,要更新一下
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1,sum[belong[i]]+=a[i];///第i个数所属的块及每块的和
}
void update(int x,int y,int c){
    if(belong[x]==belong[y])   ///x和y属于同一块时
    {
        for(int i=x;i<=y;i++)   ///暴力更新
            a[i]+=c;
        sum[belong[x]]+=(y-x+1)*c;   ///区间和更新
        return ;
    }
    for(int i=x;i<=r[belong[x]];i++)   ///不完整块,暴力更新
        a[i]+=c;
    sum[belong[x]]+=(r[belong[x]]-x+1)*c;   ///区间和更新
    for(int i=belong[x]+1;i<belong[y];i++)   ///完整块,懒惰标记更新
        lazy[i]+=c;
    for(int i=l[belong[y]];i<=y;i++)   ///不完整块,暴力更新
        a[i]+=c;
    sum[belong[y]]+=(y-l[belong[y]]+1)*c;   ///区间和更新
}
int query(int x){
    return a[x]+lazy[belong[x]];   ///结果是本身加上懒惰标记
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    while(n--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0) update(l,r,c);   ///更新操作
        else printf("%d\n",query(r));   ///查询操作
    }
    return 0;
}

入门2:区间加法,询问某区间内小于x的元素数量

传送门
题意: n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数
解题思路:既然要询问到小于某个数的个数,那就要涉及到排序问题了,对于不完整块,暴力查找;对于完整块,用二分查找或使用stl中的lower_bound都可;前提是每一块都要放在对应的容器vector中;这里有一个问题是在一个完整块中加上一个数时,不改变块内元素的相对大小,所以不需要重新排序;如果是不完整块,那就要暴力更新,重新排序啦,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn =1e6 + 10;
int n,m;
int block;///块的数量
int num;///每块中的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];   ///原数组
int lazy[maxn];   ///懒惰标记
int belong[maxn]; ///第i个元素属于哪一块
vector<int> v[1010];
void build(){
    block=sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        v[belong[i]].push_back(a[i]);   ///将a[i]放入第i个数属于的第belong[i]块容器中
    }
    for(int i=1;i<=num;i++)
    {
        sort(v[i].begin(),v[i].end());   ///对每块中元素排序
    }
}
void resort(int u){   ///重新排序
    v[u].clear();
    for(int i=l[u];i<=r[u];i++) v[u].push_back(a[i]);
    sort(v[u].begin(),v[u].end());
}
void update(int x,int y,int c){
    if(belong[x]==belong[y])   ///x和y属于同一块
    {
        for(int i=x;i<=y;i++) a[i]+=c;   ///暴力更新
        resort(belong[x]);   ///重新排序
        return ;
    }
    for(int i=x;i<=r[belong[x]];i++) a[i]+=c;   
    for(int i=belong[x]+1;i<belong[y];i++) lazy[i]+=c;   ///更新懒惰标记,不用重新排序
    for(int i=l[belong[y]];i<=y;i++) a[i]+=c;
    resort(belong[x]);
    resort(belong[y]);
}
int query(int x,int y,int c){
    int ans=0;
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++)
            if(a[i]+lazy[belong[i]]<c) ans++;   ///暴力判断,莫忘懒惰标记
        return ans;
    }
    for(int i=x;i<=r[belong[x]];i++)
        if(a[i]+lazy[belong[i]]<c) ans++;   ///暴力判断,莫忘懒惰标记
    for(int i=belong[x]+1;i<belong[y];i++)
        ans+=lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin();   ///查找每一块中满足题意得元素个数
    for(int i=l[belong[y]];i<=y;i++)
        if(a[i]+lazy[belong[i]]<c) ans++;   ///暴力判断,莫忘懒惰标记
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    while(n--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0)
            update(l,r,c);
        else
            printf("%d\n",query(l,r,c*c));
    }
    return 0;
}

入门3:区间加法,询问某区间内小于x的前驱(比其小的最大元素)

传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)
解题思路:这里要询问某个元素的前驱,和上一个题思路差不多,也是需要排序加二分或lower_bound查找,没啥不同的,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];   ///原序列
int belong[maxn]; ///第i个元素属于哪一块
int tot=0;
int lazy[maxn];   ///懒惰标记
vector<int> v[1010];
void build(){
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1,v[belong[i]].push_back(a[i]);   ///加入元素
    for(int i=1;i<=num;i++) sort(v[i].begin(),v[i].end());   ///初始排序
}
void resort(int x){
    v[x].clear();
    for(int i=l[x];i<=r[x];i++)
        v[x].push_back(a[i]);
    sort(v[x].begin(),v[x].end());
}
void update(int x,int y,int c){
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++) a[i]+=c;
        resort(belong[x]);
        return ;
    }
    for(int i=x;i<=r[belong[x]];i++) a[i]+=c;
    resort(belong[x]);
    for(int i=belong[x]+1;i<belong[y];i++) lazy[i]+=c;
    for(int i=l[belong[y]];i<=y;i++) a[i]+=c;
    resort(belong[y]);
}
int query(int x,int y,int c){
    int ans=-1;
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++)
        {
            if(a[i]+lazy[belong[x]]<c)
            {
                ans=max(ans,a[i]+lazy[belong[x]]);   ///不完整块,暴力查找答案,莫忘懒惰标记
            }
        }
        return ans;
    }
    for(int i=x;i<=r[belong[x]];i++)
    {
        if(a[i]+lazy[belong[x]]<c)
            ans=max(ans,a[i]+lazy[belong[x]]);
    }
    for(int i=belong[x]+1;i<belong[y];i++)
    {
        int pos=lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin();///pos记录答案的位置,只有pos>=1时,答案才存在,vector的第一个元素的下标
        if(pos>=1) ans=max(ans,v[i][pos-1]+lazy[i]);
    }
    for(int i=l[belong[y]];i<=y;i++)
    {
        if(a[i]+lazy[belong[y]]<c)
            ans=max(ans,a[i]+lazy[belong[y]]);
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    while(n--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0) update(l,r,c);
        else printf("%d\n",query(l,r,c));
    }
    return 0;
}

入门4:区间加法,区间求和

传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和
树状数组Code:

#include<bits/stdc++.h>
#include<iostream>
#include<stack>
#include<string>
#include<algorithm>
using namespace std;
const int maxn = 1e6 + 10;
typedef long long ll;
ll n,m,c;
ll a[maxn],sum1[maxn],sum2[maxn];
ll lowbit(ll x){
    return x&(-x);
}
void update(ll sum[],ll x,ll c){
    for(ll i=x;i<=n;i+=lowbit(i)) sum[i]+=c;
}
ll sums(ll sum[],ll x){
    ll res=0;
    for(ll i=x;i>0;i-=lowbit(i)) res=(res+sum[i]);
    return res;
}
ll get_sum(ll x){
    return sums(sum1,x)*(x+1)-sums(sum2,x);
}
int main(){
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++) scanf("%d",&a[i]);
    for(ll i=1;i<=n;i++)
    {
        ll b=a[i]-a[i-1];
        update(sum1,i,b);
        update(sum2,i,i*b);
    }
    for(ll i=1;i<=n;i++){
        ll opt,l,r;
        scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
        if(opt==0){
            update(sum1,l,c);
            update(sum1,r+1,-c);
            update(sum2,l,l*c);
            update(sum2,r+1,(r+1)*(-c));
        }
        else{
            printf("%lld\n",(get_sum(r)-get_sum(l-1))%(c+1));
        }
    }
    return 0;
}

线段树Code:
解题思路:用一个和数组维护好每个块的元素和就好了,我也没清楚这里的懒惰标记数组为啥要开long long,卡了好久,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];///原数组
int belong[maxn]; ///第i个元素属于哪一块
ll lazy[maxn];///每块额外的和标记
ll sum[maxn];///每块的和
void build(){
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        sum[belong[i]]+=a[i];   ///初始化每一块的和
    }
}
void update(int x,int y,int c){
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++)
            a[i]+=c;
        sum[belong[x]]+=(y-x+1)*c; ///更新区间和
        return ;
    }
    for(int i=x;i<=r[belong[x]];i++)
        a[i]+=c;
    sum[belong[x]]+=(r[belong[x]]-x+1)*c;///更新区间和
    for(int i=belong[x]+1;i<belong[y];i++)
        lazy[i]+=c;   ///更新懒惰标记
    for(int i=l[belong[y]];i<=y;i++)
        a[i]+=c;
    sum[belong[y]]+=(y-l[belong[y]]+1)*c;///更新区间和
}
int query(int x,int y,int c){
    int ans=0;
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++)
            ans=(ans+a[i]+lazy[belong[i]])%c;   ///莫忘懒惰标记
        return ans;
    }
    for(int i=x;i<=r[belong[x]];i++)
        ans=(ans+a[i]+lazy[belong[i]])%c;
    for(int i=belong[x]+1;i<belong[y];i++)
        ans=(ans+sum[i]+lazy[i]*block)%c;   ///整个块的懒惰标记和就是lazy[i]*block
    for(int i=l[belong[y]];i<=y;i++)
        ans=(ans+a[i]+lazy[belong[i]])%c;
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    while(n--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0) update(l,r,c);
        else printf("%d\n",query(l,r,c+1));
    }
    return 0;
}

入门5:区间开方,区间求和

传送门
题意:给出一个长为 n 的数列 a1,a2,…,an,以及 n 个操作,操作涉及区间开方,区间求和
解题思路:用普通的方法做肯定超时,每个区间开平方的操作没有规律可循,也没法标记;这里就有一个小细节,观察数据范围,每个数最多开方5~6次就会变为1,最后的数组中大部分就是1或0,于是我们可以对为1或0的区间打上标记,再遇到开方操作,直接跳过即可,实在是妙啊,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn =1e6 + 10;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];   ///原数列
int sum[maxn];   ///记录区间和
int belong[maxn]; ///第i个元素属于哪一块
bool vis[maxn];   ///标记数组(某一块全部为0或全部为1或部分为1,部分为0)
vector<ll> v[1000];
void build(){
    block=sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++) l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        sum[belong[i]]+=a[i];
    }
}
void update(int x,int y){
    int temp=0;
    if(belong[x]==belong[y])
    {
        if(vis[belong[x]]) return ;   ///如果该区间被标记,直接返回
        for(int i=x;i<=y;i++)
        {
            temp+=a[i]-(int)sqrt(a[i]);   ///单点修改
            a[i]=(int)sqrt(a[i]);
        }
        sum[belong[x]]-=temp;   ///区间和更新
        return ;
    }
    if(!vis[belong[x]])
    {
        for(int i=x;i<=r[belong[x]];i++)
        {
            temp+=a[i]-(int)sqrt(a[i]);
            a[i]=(int)sqrt(a[i]);
        }
        sum[belong[x]]-=temp;
    }
 
    for(int i=belong[x]+1;i<belong[y];i++)
    {
        if(vis[i]) continue;
        temp=0;
        for(int j=l[i];j<=r[i];j++)
        {
            temp+=a[j]-(int)sqrt(a[j]);
            a[j]=(int)sqrt(a[j]);
        }
        if(temp==0) vis[i]=true;///此操作为整个代码中唯一更新标记数组的地方,比较巧妙,twmp==0时,表示此区间可以被标记
        sum[i]-=temp;
    }
    temp=0;
    if(!vis[belong[y]])
    {
        for(int i=l[belong[y]];i<=y;i++)
        {
            temp+=a[i]-(int)sqrt(a[i]);
            a[i]=(int)sqrt(a[i]);
        }
        sum[belong[y]]-=temp;
    }
}
int query(int x,int y){
    int ans=0;
    if(belong[x]==belong[y])
    {
        for(int i=x;i<=y;i++) ans+=a[i];   ///暴力求解
        return ans;
    }
    for(int i=x;i<=r[belong[x]];i++) ans+=a[i];
    for(int i=belong[x]+1;i<belong[y];i++) ans+=sum[i];  
    for(int i=l[belong[y]];i<=y;i++) ans+=a[i];
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) cin >> a[i];
    build();
    while(n--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0)
            update(l,r);
        else
            printf("%d\n",query(l,r));
    }
    return 0;
}

入门6:单点插入,单点查询,数据随机生成

传送门
题意:出一个长为 n 的数列,以及 n 个操作,操作涉及单点插入,单点询问,数据随机生成
解题思路:对于单点插入的话,我们可以把每个块的元素放到一个二维数组中,每次插入的时候就直接暴力插入,该块后面的元素后移;这里有一个问题,如果在一个地方多次插入元素,那么复杂度就会非常高,所以还要涉及到重新分块,每当插入元素的数量等于一个块的大小时,就重新分块,确保复杂度可以控制,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[1010][3010];   ///用来存放元素的二维数组
int b[1010][3010];   ///重新分块时暂时储存元素的数组
int belong[maxn]; ///第i个元素属于哪一块
int tot=0;   ///记录插入的数的数量是否达到block
int len[maxn];   ///表示每个块的长度
void build(){
    m=n;
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=n;i++)
    {
        int kuai=(i-1)/block+1;
        int x;
        scanf("%d",&x);
        a[kuai][++len[kuai]]=x;   ///向二维数组中添加元素
    }
}
void rebuild(){   ///重新分块
    m+=tot;   ///m记录此时总元素的数量
    tot=0;   ///更新tot
    block=(int)sqrt(m);
    int cnt=0,lenth=0;
    for(int i=1;i<=num;i++)
    {
        for(int j=1;j<=len[i];j++)
        {
            cnt++;
            int id=(cnt-1)/block+1;
            b[id][++lenth]=a[i][j];   ///b数组暂时存放
            if(lenth==block) lenth=0;   ///更新lenth
        }
    }
    num=m/block; if(m%block) num++;
    for(int i=1;i<=num;i++)
    {
        if(i!=num||m%block==0) len[i]=block;   ///更新len数组
        else len[i]=m-block*block;
        for(int j=1;j<=len[i];j++)
            a[i][j]=b[i][j];   ///元素从b数组向a数组转移
    }
}
void update(int x,int y){
    int i,pos=0;
    for(i=1;i<=num;i++)   ///找到插入元素的位置
    {
        if(pos+len[i]>=x) break;
        pos+=len[i];
    }
    len[i]++;   ///更新该块的长度
    for(int j=len[i];j>=x-pos+1;j--)
        a[i][j]=a[i][j-1];    ///该块中元素后移
    a[i][x-pos]=y;    ///插入元素
    tot++;   ///插入的元素+1
    if(tot==block) rebuild();   ///判断是否需要重新分块
}
int query(int x){
    int i,pos=0;
    for(i=1;i<=num;i++)   ///查找答案的位置
    {
        if(pos+len[i]>=x) break;
        pos+=len[i];
    }
    return a[i][x-pos];   ///返回答案
}
int main(){
    scanf("%d",&n);
    build();
    int sum=n;
    while(sum--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0) update(l,r);
        else printf("%d\n",query(r));
    }
    return 0;
}

入门7:区间乘法,区间加法,单点查询

传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间乘法,区间加法,单点询问
解题思路:加乘混合运算的话,需要确定好优先级,同时维护好两个运算的标记数组;对于完整块,加法运算直接加到它的加法标记数组中去就可,乘法运算需要同时对该块的乘法标记和加法标记更新;对于不完整块,无论是加法还是乘法,需要先下推此块的加法标记和乘法标记,再暴力更新即可,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];   ///原数组
int mul[maxn],add[maxn];  ///乘法标记数组和加法数组
int belong[maxn]; ///第i个元素属于哪一块
void build(){
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
}
void pushdown(int x){   ///下推标记
    for(int i=l[x];i<=r[x];i++)
        a[i]=(a[i]*mul[x]%mod+add[x])%mod;   ///暴力更新,同时下推加法标记数组和乘法标记数组
    mul[x]=1;   ///更新乘法标记数组
    add[x]=0;   ///更新加法标记数组
}
void update(int opt,int x,int y,ll c){
    if(opt==0){   ///加法运算
        if(belong[x]==belong[y])
        {
            pushdown(belong[x]);   ///不完整块先下推标记
            for(int i=x;i<=y;i++)
                a[i]=(a[i]+c)%mod;   ///暴力更新
            return ;
        }
        pushdown(belong[x]);   ///下推标记
        for(int i=x;i<=r[belong[x]];i++)
            a[i]=(a[i]+c)%mod;
        for(int i=belong[x]+1;i<belong[y];i++)
            add[i]=(add[i]+c)%mod;   ///更新加法标记数组
        pushdown(belong[y]);   ///下推标记
        for(int i=l[belong[y]];i<=y;i++)
            a[i]=(a[i]+c)%mod;
    }
    else{
        if(belong[x]==belong[y])   ///不完整块先下推标记
        {
            pushdown(belong[x]);
            for(int i=x;i<=y;i++)
                a[i]=a[i]*c%mod;   
            return ;
        }
        pushdown(belong[x]);   ///下推标记
        for(int i=x;i<=r[belong[x]];i++)
            a[i]=(a[i]*c)%mod;
        for(int i=belong[x]+1;i<belong[y];i++)
            mul[i]=(mul[i]*c)%mod,add[i]=(add[i]*c)%mod;   ///做乘法的时候同时更新乘法标记数组和加法标记数组
        pushdown(belong[y]);   ///下推标记
        for(int i=l[belong[y]];i<=y;i++)
            a[i]=(a[i]*c)%mod;
    }
}
int query(int x){
    return (a[x]%mod*mul[belong[x]]%mod+add[belong[x]])%mod;   ///求答案
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),mul[i]=1;   ///更新乘法标记数组
    build();
	m=n;
    while(m--){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==0||opt==1)
            update(opt,l,r,c);
        else
            printf("%d\n",query(r));
    }
    return 0;
}

入门8:区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c

传送门
**题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c **
解题思路:其中一个操作是将整个区间的元素修改,我们可以想到可以用一个bool数组记录每个块的整体修改状态,再加上一个懒惰标记该块修改后的值,注意bool数组和懒惰标记是同步的;对于完整块,直需要修改该块的整体信息;不完整块,要先下推标记,再暴力更新,细节解释见代码
Code:

#include<iostream>
#include<bits/stdc++.h>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];
int belong[maxn]; ///第i个元素属于哪一块
bool vis[maxn];   ///标记某块有没有被完全修改
int lazy[maxn];   ///记录该快要被修改后的值
void build(){
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
}
void pushdown(int x){
    if(vis[x]==false) return ;   ///如果该块没有被完全更新的标记,直接返回
    for(int i=l[x];i<=r[x];i++) a[i]=lazy[x];   ///暴力更新
    vis[x]=false;   ///更新bool数组
    lazy[x]=1e9;   ///更新懒惰标记
}
int update(int x,int y,ll c){
    int ans=0;
    if(belong[x]==belong[y])
    {
        pushdown(belong[x]);   ///先下推标记
        for(int i=x;i<=y;i++)
        {
            if(a[i]==c) ans++;  
            a[i]=c;
        }
        return ans;
    }
    pushdown(belong[x]);
    for(int i=x;i<=r[belong[x]];i++)
    {
        if(a[i]==c) ans++;
        else a[i]=c;
    }
    pushdown(belong[y]);
    for(int i=l[belong[y]];i<=y;i++)
    {
        if(a[i]==c) ans++;
        else a[i]=c;
    }
    for(int i=belong[x]+1;i<belong[y];i++)
    {
        if(vis[i])   ///如果该块被标记
        {
            if(lazy[i]==c) ans+=block;   ///如果标记正好是c,答案直接加block
            lazy[i]=c;
        }
        else{
            for(int j=l[i];j<=r[i];j++)   ///否则暴力更新求解
            {
                if(a[j]==c) ans++;
                else a[j]=c;
            }
            vis[i]=true;   ///更新bool数组
            lazy[i]=c;   ///更新懒惰标记
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    memset(vis,false,sizeof vis);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),lazy[i]=1e9; ///初始化一下懒惰标记
    build();
    m=n;
    while(m--){
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        printf("%d\n",update(l,r,c));
    }
    return 0;
}

入门9:区间询问最小众数

待更–

猜你喜欢

转载自blog.csdn.net/zhazhaxiaosong/article/details/106392806