数列分块LOJ为例

数列分块

  分块的思想就是将整体划分为多个部分,将对整体的处理,看做对每个部分处理。这样做到的优点在于,如果一次对整体处理的时间复杂度为O(n),分块后将会变为O(k),k取决为分为多少块,一般而言分为 n \sqrt{n} n 份,这时复杂度为O( n \sqrt{n} n ),而k取两个极端:1和n,会让复杂度趋近于O(n),所以k的取值,具体问题应具体分析。

  分块被誉为“暴力美学”,它实质是暴力枚举的优化,也有说分块是一种数据结构,通过分块对数据预处理,优化数据的增删查改的效率;不管怎么说,分块思想是我们需要掌握的,或者有人会反驳“分块能做的,线段树都能做,而且线段树效率更高”,的确,分块能做的,线段树、数组数组等高级数据结构优势更明显(每次操作的复杂度O( l o g 2 n ) log_2{n}) log2n) ),但是分块的代码简单,没有线段树代码冗长,在复杂度允许下不如用分块,而且有一些复杂的操作,或许要用主席树做,而分块却可以轻轻松松搞定(如果OI赛场不记得线段树或者主席树代码,用分块可以骗80%的分)

  分块的基本步骤:
1、分块预处理
2、每次查询区间 L,R ,分块如下表示 “–[—][—][—]–”
  1>如果L和R在一个块内容,直接暴力即可
  2>暴力处理左端、右端,
  3>处理中间的块

题目链接:LOJ官网 搜索分块即可,下面选取部分题进行分析。

数列分块入门1

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,单点查值。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问ar的值(l和c忽略)。
题解
  此题可以作为分块模板来入门,后面的题就是在对模板进行增改。先介绍是如何进行分块,块的数量是 n \sqrt{n} n 向下取整的值(设为S),这时就会发现必然会多出一部分元素个数不是S,那么这些部分(两端)也单独成块,假设每个元素的标号从1到n(设为 i ),那么这个元素属于哪个块,取决于 i/S 向下取整的值。
  例如数列n=12:

1 4 5 2 3 6 7 8 7 10 1 15

S=sqrt(12)=3

  分块结果为:

0 0 1 1 1 2 2 2 3 3 3 4

每一个编号对应改元素属于哪一个块,因此这样分块的好处在于,只要知道该元素的标号,就可以直接以 i/S 查询它属于哪一个块。

  按照分块步骤,首先进行预处理,用Va[maxn]数组表示每个块内的每个数增加的值,既然是分开,肯定是把一个块看做一个整体处理,那么预处理在这题就显得简单,只要初始化为0即可;
  然后判断L和R是否在同一块中,同一块直接暴力对每一元素加c即可;若不在同一块中,这时的区间L和R也不一定刚刚好是几个连续的块,所以要对两端多出的一部分进行单独处理,加c即可。再处理中间连续块的部分,对每一个块的代表值Va[maxn]对应的值加c即可。
  最后单点访问,输出 f[r]+V[r/S] 为最终答案,经过上述分析,r/S也容易理解。时间复杂度分析:如果L,R在同一块中,暴力O(S)扫一遍,若不在同一块,1、扫左端部分O(S) ;2、扫右端部分O(S);3、扫中间块,中间块最多有S个(因为原来分成sqrt(n),所以完整块的数量和块的总数是相等的),所以O(S)。综上每次add操作复杂度为O(S),即O( n \sqrt{n} n ),总复杂度为O(n n \sqrt{n} n )。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 50005
#define maxm 2000005
#define INF 2147483640
#define IOS ios::sync_with_stdio(false)
#define mod 1000000007

int S;
int ff[maxn], Va[maxn];
void query_add(int L, int R, int c) {
    int ka = L / S;
    int kb = R / S;
    if (ka == kb)  //表示在某一个块内部,则暴力模拟即可
    {
        for (int i = L; i <= R; i++) ff[i] += c;
    } else {
        for (int i = L; i < (ka + 1) * S; i++)  //  --[---][---][---]-- 处理左边多出来的
            ff[i] += c;

        for (int i = ka + 1; i < kb; i++)  //处理中间的块
            Va[i] += c;

        for (int i = kb * S; i <= R; i++)  //  --[---][---][---]-- 处理右边边多出来的
            ff[i] += c;
    }
}

int main() {
    IOS;
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> ff[i];
    S = sqrt(n);

    int T = n;
    while (T--) {
        int opt, l, r, c;
        cin >> opt >> l >> r >> c;

        if (opt == 0)
            query_add(l, r, c);
        else
            cout << ff[r] + Va[r / S] << "\n";
    }

    return 0;
}

数列分块入门2

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]中小于c平方的数字个数。
题解
  经过第一题的分析,分块的模板基本一致,因为区间加法转换为块整体加法,所以不影响每个块中元素的相对大小,要记录小于某个数的个数,用二分即可,所以需要保证每个块有序。
  预处理:对每个块排序,需要注意sort排序是左闭右开的,每个块的左端点是(设i为第几个块)i*S,右端点是(i+1)*S-1
  这里涉及两个区间操作,一个是区间加法,一个区间查询,所以分类讨论,首先是区间加法:L和R在同一区间,暴力加c即可,但是有个问题,不是一整个区间的加法会破坏原来的有序性,因此还需要对这个块再排序,O(S l o g 2 S log_2{S} log2S);L和R跨越多个块,同理左右两端单独暴力,再排一次序,处理中间的块,每个块之间加c即可。所以区间加c的总复杂度为O(S l o g 2 S log_2{S} log2S)。需要注意,原数组与块有序数组需要分开记录,来保证位置不变性。
  区间查询:思路很简单,LR同一块以及两端的情况暴力即可,O(S),处理中间块每一个块都二分,复杂度为O(S l o g 2 S log_2{S} log2S)。那么总复杂度也是O(S l o g 2 S log_2{S} log2S)。
  综上,分块+区间二分的复杂度为O(n n \sqrt{n} n l o g 2 n log_2{\sqrt{n}} log2n )。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 50005
#define maxm 2000005
#define INF 2147483640
#define IOS ios::sync_with_stdio(false)
#define mod 1000000007

int S,n;
int ff[maxn],Va[maxn];
int ff2[maxn]; // 分块排序数组

void update(int id)   //对id块进行排序
{
    int L=max(id*S,1),R=min((id+1)*S,n+1);

    for(int i=L;i<R;i++)
        ff2[i]=ff[i];
    sort(ff2+L,ff2+R);
}

void query_add(int L,int R,int c)
{
    int ka=L/S;
    int kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(int i=L;i<=R;i++)
            ff[i]+=c;
        update(ka);
    }
    else
    {
        for(int i=L; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
            ff[i]+=c;
        update(ka);
        for(int i=ka+1; i<kb; i++)    //处理中间的块
            Va[i]+=c;
        for(int i=kb*S; i<=R; i++)  //  --[---][---][---]-- 处理右边边多出来的
            ff[i]+=c;
        update(kb);
    }
}

int query_ans(int L,int R,int c)
{
    c=c*c;
    int ka=L/S,kb=R/S,res=0;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(int i=L;i<=R;i++)
            if(ff[i]+Va[ka]<c)
                res++;
    }
    else
    {
        for(int i=L;i<(ka+1)*S;i++)
            if(ff[i]+Va[ka]<c)
                res++;

        for(int i=ka+1;i<kb;i++)
            res+=lower_bound(ff2+i*S,ff2+i*S+S,c-Va[i])-(ff2+i*S);

        for(int i=kb*S;i<=R;i++)
            if(ff[i]+Va[kb]<c)
                res++;
    }
    return res;
}

int main()
{
    IOS;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>ff[i];
        ff2[i]=ff[i];
    }

    S=sqrt(n);
    //--[---][---][---]--两端的不用考虑,对中间的块进行排序
    for(int i=S;i+S-1<=n;i+=S)
        sort(ff2+i,ff2+i+S);

    int T=n;
    while(T--)
    {
        int opt,l,r,c;
        cin>>opt>>l>>r>>c;

        if(opt==0)
            query_add(l,r,c);
        else
            cout<<query_ans(l,r,c)<<"\n";
    }

    return 0;
}

数列分块入门3

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x的前驱(比其小的最大元素)。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]中c的前驱的值(不存在则输出 -1)
题解
这个题不多说,用二分仍然可以解决,做法同上一题,只不过二分自己实现了一下。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define maxm 2000005
#define INF 214748364012
#define IOS ios::sync_with_stdio(false)
#define mod 1000000007

ll S,n;
ll ff[maxn],Va[maxn];
ll ff2[maxn]; // 分块排序数组

ll bound(ll id,ll c)  //查询id块为c的前驱
{
    ll l = id*S-1, r = (id+1)*S-1;
    while (l<r)
    {
        ll mid = (l + r + 1) / 2;
        if (ff2[mid]<c)l = mid;
        else
            r = mid - 1;
    }
    if(l!=(id*S-1))
        return ff2[l]+Va[id];
    else
        return -1;
}

void update(ll id)
{
    ll L=max(id*S,1LL),R=min((id+1)*S,n+1);
    for(ll i=L; i<R; i++)
        ff2[i]=ff[i];
    sort(ff2+L,ff2+R);
}

void query_add(ll L,ll R,ll c)
{
    ll ka=L/S;
    ll kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=L; i<=R; i++)
            ff[i]+=c;
        update(ka);
    }
    else
    {
        for(ll i=L; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
            ff[i]+=c;
        update(ka);
        for(ll i=ka+1; i<kb; i++)    //处理中间的块
            Va[i]+=c;
        for(ll i=kb*S; i<=R; i++)  //  --[---][---][---]-- 处理右边边多出来的
            ff[i]+=c;
        update(kb);
    }
}

ll query_ans(ll L,ll R,ll c)
{
    ll ka=L/S,kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        ll Max=-INF;
        for(ll i=L; i<=R; i++)
            if(ff[i]+Va[ka]<c)
                Max=max(Max,ff[i]+Va[ka]);
        if(Max==-INF)
            return -1;
        return Max;
    }
    else
    {
        ll Max=-INF;
        for(ll i=L; i<(ka+1)*S; i++)
            if(ff[i]+Va[ka]<c)
                Max=max(Max,ff[i]+Va[ka]);
        for(ll i=ka+1; i<kb; i++)
            Max=max(Max,bound(i,c-Va[i]));
        for(ll i=kb*S; i<=R; i++)
            if(ff[i]+Va[kb]<c)
                Max=max(Max,ff[i]+Va[kb]);
        if(Max==-INF)
            return -1;
        return Max;
    }
}

int main()
{

    IOS;
    cin>>n;
    for(ll i=1; i<=n; i++)
    {
        cin>>ff[i];
        ff2[i]=ff[i];
    }

    S=sqrt(n);
    //--[---][---][---]--两端的不用考虑,对中间的块进行排序
    for(ll i=S; i+S-1<=n; i+=S)
        sort(ff2+i,ff2+i+S);

    ll T=n;
    while(T--)
    {
        ll opt,l,r,c;
        cin>>opt>>l>>r>>c;

        if(opt==0)
            query_add(l,r,c);
        else
            cout<<query_ans(l,r,c)<<"\n";
    }

    return 0;
}

数列分块入门4

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]的所有数字的和 mod(c+1)
题解
用两个数组分别标记,块增值、块的和,考虑加法对块的和的影响
初始化:算出每个块的和,记为Vs[maxn]
区间加法:需要注意LR在同一块,以及对两端处理时,需要更新Vs数组,其他做法一致。
查询区间和:过程和原始的加法一致
复杂度:O(n n \sqrt{n} n )

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define maxm 2000005
#define INF 2147483640
#define IOS ios::sync_with_stdio(false)


ll S,n;
//Va记录每个块增加的值,Vs记录每个块的和且与Va独立,即Va增加没有对Vs影响,或者说当前块ff值的和
ll ff[maxn],Va[maxn],Vs[maxn];

void query_add(ll L,ll R,ll c)
{
    ll ka=L/S;
    ll kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=L; i<=R; i++)
            ff[i]+=c,Vs[ka]+=c;
    }
    else
    {
        for(ll i=L; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
            ff[i]+=c,Vs[ka]+=c;

        for(ll i=ka+1; i<kb; i++)    //处理中间的块
            Va[i]+=c;

        for(ll i=kb*S; i<=R; i++)  //  --[---][---][---]-- 处理右边边多出来的
            ff[i]+=c,Vs[kb]+=c;
    }
}

ll query_sum(ll L,ll R,ll c)
{
    ll mod=c+1;
    ll ka=L/S,kb=R/S,res=0;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=L; i<=R; i++)
            res=(res+ff[i]+Va[ka])%mod;
    }
    else
    {
        for(ll i=L; i<(ka+1)*S; i++)
            res=(res+ff[i]+Va[ka])%mod;

        for(ll i=ka+1; i<kb; i++)
            res=(res+Vs[i]+Va[i]*S%mod)%mod;

        for(ll i=kb*S; i<=R; i++)
            res=(res+ff[i]+Va[kb])%mod;
    }
    return res;
}

int main()
{

    IOS;
    cin>>n;

    for(ll i=1; i<=n; i++)
        cin>>ff[i];

    S=sqrt(n);
    for(int i=1;i<=n;i++)
        Vs[i/S]+=ff[i];

    ll T=n;
    while(T--)
    {
        ll opt,l,r,c;
        cin>>opt>>l>>r>>c;

        if(opt==0)
            query_add(l,r,c);
        else
            cout<<query_sum(l,r,c)<<"\n";
    }

    return 0;
}

数列分块入门5

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间开方,区间求和。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都开方(向下取整)。对于区间中每个在这里插入图片描述
若opt=1,表示询问[l,r]的所有数字的和
题解
  刚开始看到这个题,在想“和的开方”与“开方的和”有什么联系,发现没有联系,无法快速的进行转化。
  因为开方会破坏块的和,所以需要考虑开方对求和标记的影响。之后发现在经过32次开方后(输入数据范围为int),会变成0或者1,既然如此,这个块若经过了大于31次开方,就不再对块每个元素求和,所以可以看出在对连续块进行开方时,每个块最多进行了31次开方操作,即复杂度为O(31n),对于总复杂度而言是微不足道的。总复杂度还是O(n n \sqrt{n} n )

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define maxm 2000005
#define INF 2147483640
#define IOS ios::sync_with_stdio(false)


ll S,n;
//Va记录每个块被开方的次数,Vs记录每个块的和且与Va独立,即Va改变没有对Vs影响,或者说当前块ff值的和
ll ff[maxn],Va[maxn],Vs[maxn];

void query_sqrt(ll L,ll R)
{
    ll ka=L/S;
    ll kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=L; i<=R; i++)
            ff[i]=sqrt(ff[i]);
        Vs[ka]=0;
        for(int i=ka*S; i<(ka+1)*S; i++)
            Vs[ka]+=ff[i];
    }
    else
    {
        Vs[ka]=0;
        for(int i=ka*S; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
        {
            if(i>=L)
                ff[i]=sqrt(ff[i]);
            Vs[ka]+=ff[i];
        }

        for(ll i=ka+1; i<kb; i++)    //处理中间的块
        {
            Va[i]++;
            if(Va[i]<32)
            {
                for(int k=i*S;k<(i+1)*S;k++)
                    ff[k]=sqrt(ff[k]);

                Vs[i]=0;
                for(int k=i*S;k<(i+1)*S;k++)
                    Vs[i]+=ff[k];
            }
        }

        Vs[kb]=0;
        //注意右端点,防止re
        for(ll i=kb*S; i<min((kb+1)*S,n+1); i++)  //  --[---][---][---]-- 处理右边边多出来的
        {
            if(i<=R)
                ff[i]=sqrt(ff[i]);
            Vs[kb]+=ff[i];
        }
    }
}

ll query_sum(ll L,ll R)
{
    ll ka=L/S,kb=R/S,res=0;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=L; i<=R; i++)
            res+=ff[i];
    }
    else
    {
        for(ll i=L; i<(ka+1)*S; i++)
            res+=ff[i];

        for(ll i=ka+1; i<kb; i++)
            res+=Vs[i];

        for(ll i=kb*S; i<=R; i++)
            res+=ff[i];
    }
    return res;
}

int main()
{

    IOS;
    cin>>n;

    for(ll i=1; i<=n; i++)
        cin>>ff[i];

    S=sqrt(n);
    for(int i=1; i<=n; i++)
        Vs[i/S]+=ff[i];

    ll T=n;
    while(T--)
    {
        ll opt,l,r,c;
        cin>>opt>>l>>r>>c;

        if(opt==0)
            query_sqrt(l,r);
        else
            cout<<query_sum(l,r)<<"\n";
    }

    return 0;
}

数列分块入门7

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间乘法,区间加法,单点询问。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示将位于[l,r]的之间的数字都乘c。
若opt=2,表示询问 ar 的值
题解
  线段树做此题可以用懒标记对一个区间操作,每次都push,可以实现。此题若用分块来做,需分别记录每个块的增值以及乘值。记增值为Va[maxn],乘值为Vm[maxn]。那么按照先乘在加的原则,考虑操作之间的影响。

  初始化:Va初始化为0,Vm初始化为1
  加法操作:LR同块以及两端暴力的情况:因为这三种情况,是对块的某一部分进行操作的,要严格保证块的先乘后加,所以需要把当前块Vm代入原数组设为ff[maxn],Vm并转化为1,因为Va是加法,不涉及先后顺序,所以可以不动,处理完之后再对 ff[maxn] 数组增值。处理中间连续的块,只需要对Va增值即可。
  乘法操作: 当前值表示为 ff[i]*Vm[i/S]+Va[i/S] 那么如果再乘以一个c,则表示为 ff[i]*Vm[i/S]*c+Va[i/S] *c ,从公式可以看出,处理中间连续的块,需要对Vm以及Va都乘c;同样在处理LR同块以及两端暴力的情况时,因为是块的部分值操作,需要将 ff[i]转换成ff[i]*Vm[i/S]+Va[i/S]。复杂度:O(n n \sqrt{n} n )

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define maxm 2000005
#define INF 2147483640
#define IOS ios::sync_with_stdio(false)
#define mod 10007

ll S,n;
//Va记录每个块相加的值,Vm记录每个块的乘积
ll ff[maxn],Va[maxn],Vm[maxn];

void query_add(ll L,ll R,ll c)
{
    ll ka=L/S;
    ll kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=ka*S;i<min((ka+1)*S,n+1);i++)
        {
            ff[i]*=Vm[ka];
            if(i>=L&&i<=R)
                ff[i]+=c;
            ff[i]%=mod;
        }
        Vm[ka]=1;
    }
    else
    {
        for(int i=ka*S; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
        {
            ff[i]*=Vm[ka];
            if(i>=L)
                ff[i]+=c;
            ff[i]%=mod;
        }
        Vm[ka]=1;

        for(ll i=ka+1; i<kb; i++)    //处理中间的块
        {
            Va[i]+=c;
            Va[i]%=mod;
        }

        //注意右端点,防止re
        for(ll i=kb*S; i<min((kb+1)*S,n+1); i++)  //  --[---][---][---]-- 处理右边边多出来的
        {
            ff[i]*=Vm[kb];
            if(i<=R)
                ff[i]+=c;
            ff[i]%=mod;

        }
        Vm[kb]=1;
    }
}

void query_mul(ll L,ll R,ll c)
{
    ll ka=L/S,kb=R/S;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        for(ll i=ka*S;i<min((ka+1)*S,n+1);i++)
        {
            ff[i]=ff[i]*Vm[ka]+Va[ka];
            if(i>=L&&i<=R)
                ff[i]*=c;
            ff[i]%=mod;
        }
        Vm[ka]=1;
        Va[ka]=0;
    }
    else
    {

        for(int i=ka*S; i<(ka+1)*S; i++) //  --[---][---][---]-- 处理左边多出来的
        {
            ff[i]=ff[i]*Vm[ka]+Va[ka];
            if(i>=L)
                ff[i]*=c;
            ff[i]%=mod;
        }
        Vm[ka]=1;
        Va[ka]=0;

        for(ll i=ka+1; i<kb; i++)
        {
            Vm[i]*=c;
            Va[i]*=c;
            Vm[i]%=mod;
            Va[i]%=mod;
        }

        //注意右端点,防止re
        for(ll i=kb*S; i<min((kb+1)*S,n+1); i++)  //  --[---][---][---]-- 处理右边边多出来的
        {
            ff[i]=ff[i]*Vm[kb]+Va[kb];
            if(i<=R)
                ff[i]*=c;
            ff[i]%=mod;
        }
        Vm[kb]=1;
        Va[kb]=0;
    }
}

int main()
{

    IOS;
    cin>>n;
    for(ll i=1; i<=n; i++)
        cin>>ff[i];

    S=sqrt(n);
    for(int i=1;i<=n;i++)
        Vm[i/S]=1;

    ll T=n;
    while(T--)
    {
        ll opt,l,r,c;
        cin>>opt>>l>>r>>c;

        if(opt==0)
            query_add(l,r,c%mod);
        else if(opt==1)
            query_mul(l,r,c%mod);
        else
            cout<<(ff[r]*Vm[r/S]%mod+Va[r/S])%mod<<"\n";
    }
    return 0;
}

数列分块入门8

题目描述
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c。
输入格式
第一行输入一个数字n。
第二行输入n个数字,第i个数字为 ai,以空格隔开。
接下来输入 行询问,每行输入四个数字l,r,c,以空格隔开。表示徐闻位于[l,r]的之间的等于c数字,并将该区间的数字全改为c。

题解
  如果某一个块的所有元素为某一个值,访问这个块只需要这个块的大小即可算出等于数c的个数。因此这里需要维护这个块的所有元素是否相等,若相等则直接记录该数,不相等则用一个-INF标记一下。计算所有元素相等的块某个数的个数,直接看标记即可;计算所有元素不等的块某个数的个数,还是需要暴力扫一遍这个块。假设经过若干次处理所有完整的块的每个元素都等于某一个值,记这个初始状态,因此在这个基础上再进行操作,只会破坏,两端的块的状态,使其块内元素变为不相等,标记为-INF,而中间连续的块需要判断这个块是否为-INF,是,则暴力扫描,不是,则通过标记可以判断这个块所有的元素的值。
复杂度O(n n \sqrt{n} n ),上一次破坏的状态,相当于埋了一个炸弹,下一次扫描中间连续块的,要对这个炸弹进行暴力扫描,因此每次仅仅多了两个O(S)。一次操作大约O(5S),所以相对来书,总复杂度不变,只是常数变大了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define maxm 2000005
#define INF 2147483640123
#define IOS ios::sync_with_stdio(false)
#define mod 10007

ll S,n;
//Va记录每个块当前值
ll ff[maxn],Va[maxn];

ll query(ll L,ll R,ll c)
{
    ll ka=L/S,kb=R/S,res=0;
    if(ka==kb) //表示在某一个块内部,则暴力模拟即可
    {
        if(Va[ka]==-INF)
        {
            for(int i=L; i<=R; i++)
            {
                if(ff[i]==c)
                    res++;
                ff[i]=c;
            }
        }
        else
        {
            if(Va[ka]==c)
                res+=(R-L+1);
            else
            {
                for(int i=ka*S; i<min((ka+1)*S,n+1); i++)
                {
                    if(i>=L&&i<=R)
                        ff[i]=c;
                    else
                        ff[i]=Va[ka];
                }
                Va[ka]=-INF;
            }
        }
    }
    else
    {
        if(Va[ka]==-INF)
        {
            for(int i=L; i<(ka+1)*S; i++)//  --[---][---][---]-- 处理左边多出来的
            {
                if(ff[i]==c)
                    res++;
                ff[i]=c;
            }
        }
        else
        {
            if(Va[ka]==c)
                res+=((ka+1)*S-L);
            else
            {
                for(int i=ka*S; i<min((ka+1)*S,n+1); i++)
                {
                    if(i>=L)
                        ff[i]=c;
                    else
                        ff[i]=Va[ka];
                }
                Va[ka]=-INF;
            }
        }

        for(ll i=ka+1; i<kb; i++)    //处理中间的块
        {
            if(Va[i]==c)
                res+=S;
            else if(Va[i]!=-INF)
                Va[i]=c;
            else
            {
                for(ll j=i*S;j<(i+1)*S;j++)
                    if(ff[j]==c)
                        res++;
                Va[i]=c;
            }

        }


        if(Va[kb]==-INF)
        {
            //注意右端点,防止re
            for(ll i=kb*S; i<=R; i++)  //  --[---][---][---]-- 处理右边边多出来的
            {
                if(ff[i]==c)
                    res++;
                ff[i]=c;
            }
        }
        else
        {
            if(Va[kb]==c)
                res+=(R-kb*S+1);
            else
            {
                for(int i=kb*S; i<min((kb+1)*S,n+1); i++)
                {
                    if(i<=R)
                        ff[i]=c;
                    else
                        ff[i]=Va[kb];
                }
                Va[kb]=-INF;
            }
        }
    }

    return res;
}


int main()
{

    IOS;
    cin>>n;
    for(ll i=1; i<=n; i++)
        cin>>ff[i];

    S=sqrt(n);
    for(int i=1; i<=n; i++)  //-INF标记为该块的值存在不相同的元素
        Va[i/S]=-INF;

    ll T=n;
    while(T--)
    {
        ll l,r,c;
        cin>>l>>r>>c;
        cout<<query(l,r,c)<<"\n";
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值