以前一直听说分块啥的…感觉不明觉厉…昨天晚上突发奇想想学习一下然后搜博客的时候发现了qscqesze的bilbili视频…讲得真好orz虽然很久之前就关注他了但是没有好好看过他的视频orz

传送门:https://www.bilibili.com/video/av6445624/

昨天晚上看完的今天早上就迫不及待敲一发找到了loj上hzwer的数列分块入门打算都做一遍hhh

分块是什么呢?就是把一个大块拆成若干个小块进行计算,每个小块有可能有一些共同特点,或者每个小块内部是有顺序的,这样,在修改操作的时候,只需要算出两边的节点所属的小块的编号分别是什么,然后两边的散块可以进行暴力修改,而中间的由于题目不同,可以进行不同的操作,比如说区间加,那么就可以在中间的每个小块上面直接像线段树一样打一个lazy标记,标记区间增加了多少。然后在查询的时候就可以直接对散块进行暴力查询,然后对整块运用之前设置的lazy标记,或者保证有序后用二分进行从O(n)进行到O(log(n))的优化。

build函数
int n;
long long a[50010];
int num;//分块的个数
int belong[50010];//每个数属于哪个分块
int block;//每个块的大小
int lef[50010],rig[50010];//该块的左/右端点位置
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
    for(int i=1;i<=num;i++)
        lef[i]=(i-1)*block+1,rig[i]=i*block;
    rig[num]=n;//最后一块不满的情况
}
LOJ 6277

题意:若 opt=0,表示将位于[l,r]的之间的数字都加c。

若 opt=1,表示询问ar的值(l和c忽略)。

思路:加一个数组表示每个块一起又加了多少即可。

代码:

int n;
long long a[50010];
int num;//分块的个数
int belong[50010];//每个数属于哪个分块
int block;//每个块的大小
int lef[50010],rig[50010];//该块的左/右端点位置
long long d[50010];//每个块一起又加了多少
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=num;i++)
        lef[i]=(i-1)*block+1,rig[i]=i*block;
    rig[num]=n;//最后一块不满的情况
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
}
void update(int l,int r,int c)//区间更新
{
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            a[i]+=c;
    }
    else
    {
        //printf("%d %d %d %d\n",belong[l],belong[r],rig[belong[l]],lef[belong[r]]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]+=c;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i]+=c;
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]+=c;
    }
}
void ask(int x)//单点询问
{
    printf("%lld\n",a[x]+d[belong[x]]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    memset(d,0,sizeof(d));
    int ope,l,r,c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%d",&ope,&l,&r,&c);
        if(ope==0)update(l,r,c);
        else if(ope==1)ask(r);
    }
    return 0;
}
LOJ 6278

题意:若 opt=0,表示将位于[l,r] 的之间的数字都加 c。

若 opt=1,表示询问 [l,r] 中,小于 c^2 的数字的个数。

思路:

对于每次区间操作:

1.不完整的块 的O(√n)个元素怎么处理?

2.O(√n)个 整块 怎么处理?

3.要预处理什么信息(复杂度不能超过后面的操作)?

在块内维护升序数组b。

考虑到加c之后,块内的升序是不会发生改变的,但是块外是会有改变的,所以要维护块外的升序。

当询问时,块外的顺序已经乱了,b[i]的编号与询问的编号是不同的,所以不能直接根据b[i]来二分,而要暴力查询;而块内则无所谓顺序,因为都在[l,r]的范围之内,只需知道有几个,直接在b上二分即可。

代码:

int n;
long long a[50010];
long long b[50010];//块内排序
int num;//分块的个数
int belong[50010];//每个数属于哪个分块
int block;//每个块的大小
int lef[50010],rig[50010];//该块的左/右端点位置
long long d[50010];//每个块一起又加了多少
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        b[i]=a[i];
    }
    for(int i=1;i<=num;i++)
    {
        lef[i]=(i-1)*block+1,rig[i]=i*block;
        if(i==num)rig[i]=n;//最后一块不满的情况
        sort(b+lef[i],b+rig[i]+1);//块内维护升序
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",b[i]);
    printf("\n");*/
}
void update(int l,int r,long long c)//区间更新
{
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            a[i]+=c;
        for(int i=lef[belong[l]];i<=rig[belong[l]];i++)//更新b[i]
            b[i]=a[i];
        sort(b+lef[belong[l]],b+rig[belong[l]]+1);//块内维护升序
    }
    else
    {
        //printf("%d %d %d %d\n",belong[l],belong[r],rig[belong[l]],lef[belong[r]]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]+=c;
        for(int i=lef[belong[l]];i<=rig[belong[l]];i++)//更新b[i]
            b[i]=a[i];
        sort(b+lef[belong[l]],b+rig[belong[l]]+1);
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i]+=c;
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]+=c;
        for(int i=lef[belong[r]];i<=rig[belong[r]];i++)
            b[i]=a[i];
        sort(b+lef[belong[r]],b+rig[belong[r]]+1);
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",b[i]);
    printf("\n");*/
}
void ask(int l,int r,long long st)//区间询问
{
    int ans=0;
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            if(a[i]+d[belong[l]]<st)
                ans++;
    }
    else
    {
        for(int i=l;i<=rig[belong[l]];i++)
            if(a[i]+d[belong[l]]<st)
                ans++;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
        {
            int pos=lower_bound(b+lef[i],b+rig[i]+1,st-d[i])-b;
            ans+=pos-lef[i];
        }
        for(int i=lef[belong[r]];i<=r;i++)
            if(a[i]+d[belong[r]]<st)
                ans++;
    }
    printf("%d\n",ans);
}
int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    memset(d,0,sizeof(d));
    int ope,l,r;
    long long c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%lld",&ope,&l,&r,&c);
        if(ope==0)update(l,r,c);
        else if(ope==1)ask(l,r,c*c);
    }
    return 0;
}
LOJ 6279

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。

思路:跟上一题差不多区间询问的地方稍微改一下就可以了。

可以在块内维护其它结构使其更具有拓展性,比如放一个set,这样如果还有插入、删除元素的操作,会更加的方便。

分块的调试检测技巧:可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。

代码:

int n;
long long a[100010];
long long b[100010];//块内排序
int num;//分块的个数
int belong[100010];//每个数属于哪个分块
int block;//每个块的大小
int lef[100010],rig[100010];//该块的左/右端点位置
long long d[100010];//每个块一起又加了多少
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        b[i]=a[i];
    }
    for(int i=1;i<=num;i++)
    {
        lef[i]=(i-1)*block+1,rig[i]=i*block;
        if(i==num)rig[i]=n;//最后一块不满的情况
        sort(b+lef[i],b+rig[i]+1);//块内维护升序
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",b[i]);
    printf("\n");*/
}
void update(int l,int r,long long c)//区间更新
{
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            a[i]+=c;
        for(int i=lef[belong[l]];i<=rig[belong[l]];i++)//更新b[i]
            b[i]=a[i];
        sort(b+lef[belong[l]],b+rig[belong[l]]+1);//块内维护升序
    }
    else
    {
        //printf("%d %d %d %d\n",belong[l],belong[r],rig[belong[l]],lef[belong[r]]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]+=c;
        for(int i=lef[belong[l]];i<=rig[belong[l]];i++)//更新b[i]
            b[i]=a[i];
        sort(b+lef[belong[l]],b+rig[belong[l]]+1);
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i]+=c;
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]+=c;
        for(int i=lef[belong[r]];i<=rig[belong[r]];i++)
            b[i]=a[i];
        sort(b+lef[belong[r]],b+rig[belong[r]]+1);
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",a[i]+d[belong[i]]);
    printf("\n");*/
}
void ask(int l,int r,long long st)//区间询问
{
    long long cur=-INF;
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
        {
            long long zs=a[i]+d[belong[l]];
            if(zs<st&&zs>cur)
                cur=zs;
        }
    }
    else
    {
        for(int i=l;i<=rig[belong[l]];i++)
        {
            long long zs=a[i]+d[belong[l]];
            if(zs<st&&zs>cur)
                cur=zs;
        }
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
        {
            int pos=lower_bound(b+lef[i],b+rig[i]+1,st-d[i])-b;
            long long zs=b[pos-1]+d[i];
            if(pos-1>=lef[i]&&zs<st&&zs>cur)
                cur=zs;
        }
        for(int i=lef[belong[r]];i<=r;i++)
        {
            long long zs=a[i]+d[belong[r]];
            if(zs<st&&zs>cur)
                cur=zs;
        }
    }
    if(cur==-INF)printf("-1\n");
    else printf("%lld\n",cur);
}
int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    memset(d,0,sizeof(d));
    int ope,l,r;
    long long c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%lld",&ope,&l,&r,&c);
        if(ope==0)update(l,r,c);
        else if(ope==1)ask(l,r,c);
    }
    return 0;
}
LOJ 6280

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和。

思路:加两个数组表示每个块的总和(用前缀和处理),表示每个块又加了多少,维护区间和即可。

代码:

int n;
long long a[50010];
int num;//分块的个数
int belong[50010];//每个数属于哪个分块
int block;//每个块的大小
int lef[50010],rig[50010];//该块的左/右端点位置
long long pre[50010];//前缀和
long long sum[50010];//每个块的总和
long long d[50010];//每个块一起又加了多少
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    pre[0]=0;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        pre[i]=pre[i-1]+a[i];
    }
    for(int i=1;i<=num;i++)
    {
        lef[i]=(i-1)*block+1,rig[i]=i*block;
        if(i==num)rig[i]=n;//最后一块不满的情况
        sum[i]=pre[rig[i]]-pre[lef[i]-1];
    }
    /*for(int i=1;i<=num;i++)
        printf("%lld ",sum[i]);
    printf("\n");*/
}
void update(int l,int r,long long c)//区间更新
{
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            a[i]+=c,sum[belong[l]]+=c;//维护区间和
    }
    else
    {
        //printf("%d %d %d %d\n",belong[l],belong[r],rig[belong[l]],lef[belong[r]]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]+=c,sum[belong[l]]+=c;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i]+=c;
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]+=c,sum[belong[r]]+=c;
    }
}
void ask(int l,int r,long long modd)//区间询问
{
    long long ans=0;
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
            ans=(ans+a[i]+d[belong[l]])%modd;
    }
    else
    {
        for(int i=l;i<=rig[belong[l]];i++)
            ans=(ans+a[i]+d[belong[l]])%modd;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            ans=(ans+sum[i]+(d[i]*block)%modd)%modd;
        for(int i=lef[belong[r]];i<=r;i++)
            ans=(ans+a[i]+d[belong[r]])%modd;
    }
    printf("%lld\n",ans);
}
int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    memset(d,0,sizeof(d));
    int ope,l,r;
    long long c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%lld",&ope,&l,&r,&c);
        if(ope==0)update(l,r,c);
        else if(ope==1)ask(l,r,c+1);
    }
    return 0;
}
LOJ 6281

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间开方,区间求和。

思路:跟以前做过的一道线段树差不多,那道线段树没有用到lazy标记,这里也差不多,不能直接像上面那题区间求和,要利用开方,开个bool数组标记每个块是否还可以开方,如果都是0或1的话就不能开方了,这里还是用到sum数组,查询的时候会快点。

代码:

int n;
long long a[50010];
int num;//分块的个数
int belong[50010];//每个数属于哪个分块
int block;//每个块的大小
int lef[50010],rig[50010];//该块的左/右端点位置
bool can[50010];//每个块是否还可以开方
long long pre[50010];
long long sum[50010];//每个块的和
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    pre[0]=0;
    for(int i=1;i<=n;i++)
    {
        belong[i]=(i-1)/block+1;
        can[i]=true;
        pre[i]=pre[i-1]+a[i];
    }
    for(int i=1;i<=num;i++)
    {
        lef[i]=(i-1)*block+1,rig[i]=i*block;
        if(i==num)rig[i]=n;//最后一块不满的情况
        sum[i]=pre[rig[i]]-pre[lef[i]-1];
    }
    /*for(int i=1;i<=num;i++)
        printf("%lld ",sum[i]);
    printf("\n");*/
}
void update(int l,int r,long long c)//区间更新
{
    if(belong[l]==belong[r])
    {
        if(can[belong[l]])
        {
            for(int i=l;i<=r;i++)
            {
                sum[belong[l]]=sum[belong[l]]-a[i]+sqrt(a[i]);
                a[i]=sqrt(a[i]);
            }
            bool sign=false;
            for(int i=lef[belong[l]];i<=rig[belong[l]]&&!sign;i++)
                if(a[i]!=0&&a[i]!=1)sign=true;
            if(!sign)can[belong[l]]=false;
        }
    }
    else
    {
        if(can[belong[l]])
        {
            for(int i=l;i<=rig[belong[l]];i++)
            {
                sum[belong[l]]=sum[belong[l]]-a[i]+sqrt(a[i]);
                a[i]=sqrt(a[i]);
            }
            bool sign=false;
            for(int i=lef[belong[l]];i<=rig[belong[l]]&&!sign;i++)
                if(a[i]!=0&&a[i]!=1)sign=true;
            if(!sign)can[belong[l]]=false;
        }
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
        {
            if(can[i])
            {
                for(int j=lef[i];j<=rig[i];j++)
                {
                    sum[i]=sum[i]-a[j]+sqrt(a[j]);
                    a[j]=sqrt(a[j]);
                }
                bool sign=false;
                for(int j=lef[i];j<=rig[i]&&!sign;j++)
                    if(a[j]!=0&&a[j]!=1)sign=true;
                if(!sign)can[i]=false;
            }
        }
        if(can[belong[r]])
        {
            for(int i=lef[belong[r]];i<=r;i++)
            {
                sum[belong[r]]=sum[belong[r]]-a[i]+sqrt(a[i]);
                a[i]=sqrt(a[i]);
            }
            bool sign=false;
            for(int i=lef[belong[r]];i<=rig[belong[r]]&&!sign;i++)
                if(a[i]!=0&&a[i]!=1)sign=true;
            if(!sign)can[belong[r]]=false;
        }
    }
}
void ask(int l,int r,long long c)//区间询问
{
    long long ans=0;
    if(belong[l]==belong[r])
        for(int i=l;i<=r;i++)
            ans+=a[i];
    else
    {
        for(int i=l;i<=rig[belong[l]];i++)
            ans+=a[i];
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            ans+=sum[i];
        for(int i=lef[belong[r]];i<=r;i++)
            ans+=a[i];
    }
    printf("%lld\n",ans);
}
int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    int ope,l,r;
    long long c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%lld",&ope,&l,&r,&c);
        if(ope==0)update(l,r,c);
        else if(ope==1)ask(l,r,c+1);
    }
    return 0;
}
LOJ 6282

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及单点插入,单点询问,数据随机生成。

思路:每个块放入vector内,插入的时候用insert函数实现,但是如果先在一个块有大量单点插入,这个块的大小会大大超过√n,块内的插入就很慢了,所以还需要引入一个操作:重新分块(重构),每√n次插入后,重新把数列平均分一下块。

我这里有个很奇怪的地方,rebuild()这个函数里面,我直接用覆盖n的方法会WA,但是另外用个变量就AC了,不知道错在哪里,真的很奇怪…

代码:

int n;
int num;
int block;
int a[100010];
int zs[200010];
vector<int>v[100010];//每块内的元素
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
        v[(i-1)/block+1].push_back(a[i]);
}
void rebuild()
{
    int tot=0;
    for(int i=1;i<=num;i++)
    {
        for(int j=0;j<v[i].size();j++)
            zs[++tot]=v[i][j];
        v[i].clear();
    }
    block=sqrt(tot);
    num=tot/block;if(tot%block)num++;
    for(int i=1;i<=tot;i++)
        v[(i-1)/block+1].push_back(zs[i]);
}
void update(int l,int r)//在l前面加入r
{
    for(int i=1;i<=num;i++)
    {
        int can=l-v[i].size();
        if(can>0)
            l=l-v[i].size();//x不在这块
        else
        {
            v[i].insert(v[i].begin()+l-1,r);
            if(v[i].size()>20*block)rebuild();
            /*for(int j=1;j<=num;j++)
            {
                for(int k=0;k<v[j].size();k++)
                    printf("%d ",v[j][k]);
                printf("\n");
            }*/
            break;
        }
    }
}
void ask(int x)
{
    for(int i=1;i<=num;i++)
    {
        int can=x-v[i].size();
        if(can>0)x=x-v[i].size();//x不在这块
        else
        {
            printf("%d\n",v[i][x-1]);
            //printf("v[%d]\n",i);
            return;
        }
    }
}
int main()
{
    //freopen("/Users/apple/Desktop/input.txt","r",stdin);
   //freopen("/Users/apple/Desktop/output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build();
    int ope,l,r,c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%d",&ope,&l,&r,&c);
        if(ope==0)update(l,r);
        else if(ope==1)ask(r);
    }
    return 0;
}
LOJ 6283

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间乘法,区间加法,单点询问。

思路:之前线段树专题里做过差不多的题目,lazy标记变成ax+b形式即可。这里每次更新a[i],即块外的时候,要把标记下放,否则会打乱乘除的顺序,在块内则不需要,因为块内并没有改变a[i],如果块内之后变成块外的话,会下放。

代码:

int n;
long long a[100010];
int num;//分块的个数
int belong[100010];//每个数属于哪个分块
int block;//每个块的大小
int lef[100010],rig[100010];//该块的左/右端点位置
long long d[100010][2];//每个块的增量ax+b
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
    for(int i=1;i<=num;i++)
    {
        lef[i]=(i-1)*block+1,rig[i]=i*block;
        d[i][0]=1;d[i][1]=0;
    }
    rig[num]=n;//最后一块不满的情况
}
void pushdown(int x)
{
    for(int i=lef[x];i<=rig[x];i++)
    {
        a[i]=(a[i]*d[x][0])%MOD;
        a[i]=(a[i]+d[x][1])%MOD;
    }
    d[x][0]=1;d[x][1]=0;
}
void update1(int l,int r,long long c)
{
    if(belong[l]==belong[r])
    {
        if(d[belong[l]][0]!=1||d[belong[l]][1]!=0)pushdown(belong[l]);
        for(int i=l;i<=r;i++)
            a[i]=(a[i]+c)%MOD;
    }
    else
    {
        //printf("belong:%d %d\n",belong[l],belong[r]);
        if(d[belong[l]][0]!=1||d[belong[l]][1]!=0)pushdown(belong[l]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]=(a[i]+c)%MOD;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i][1]=(d[i][1]+c)%MOD;//a[i]没有改变,不需要pushdown
        if(d[belong[r]][0]!=1||d[belong[r]][1]!=0)pushdown(belong[r]);
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]=(a[i]+c)%MOD;
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",a[i]);
    printf("\n");*/
}
void update2(int l,int r,long long c)
{
    if(belong[l]==belong[r])
    {
        if(d[belong[l]][0]!=1||d[belong[l]][1]!=0)pushdown(belong[l]);
        for(int i=l;i<=r;i++)
            a[i]=(a[i]*c)%MOD;
    }
    else
    {
        //printf("belong:%d %d\n",belong[l],belong[r]);
        if(d[belong[l]][0]!=1||d[belong[l]][1]!=0)pushdown(belong[l]);
        for(int i=l;i<=rig[belong[l]];i++)
            a[i]=(a[i]*c)%MOD;
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
            d[i][0]=(d[i][0]*c)%MOD,d[i][1]=(d[i][1]*c)%MOD;
        if(d[belong[r]][0]!=1||d[belong[r]][1]!=0)pushdown(belong[r]);
        for(int i=lef[belong[r]];i<=r;i++)
            a[i]=(a[i]*c)%MOD;
    }
    /*for(int i=1;i<=n;i++)
        printf("%lld ",a[i]);
    printf("\n");*/
}
void ask(int r)
{
    long long ans=a[r];
    ans=(ans*d[belong[r]][0])%MOD;
    ans=(ans+d[belong[r]][1])%MOD;
    printf("%lld\n",ans);
}
int main()
{
    //freopen("/Users/apple/Desktop/input.txt","r",stdin);
    //freopen("/Users/apple/Desktop/output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build();
    int ope,l,r;
    long long c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%lld",&ope,&l,&r,&c);
        if(ope==0)update1(l,r,c);
        else if(ope==1)update2(l,r,c);
        else if(ope==2)ask(r);
    }
    return 0;
}
LOJ 6284

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c。

思路:维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。

假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。pushdown操作类似上题。

代码:

int n;
int a[100010];
int num;//分块的个数
int belong[100010];//每个数属于哪个分块
int block;//每个块的大小
int lef[100010],rig[100010];//该块的左/右端点位置
int same[100010];
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
    for(int i=1;i<=num;i++)
        lef[i]=(i-1)*block+1,rig[i]=i*block;
    rig[num]=n;//最后一块不满的情况
}
void pushdown(int x)
{
    for(int i=lef[x];i<=rig[x];i++)
        a[i]=same[x];
    same[x]=-1;
}
void solve(int l,int r,int c)
{
    int ans=0;
    if(belong[l]==belong[r])
    {
        if(same[belong[l]]!=-1)pushdown(belong[l]);
        for(int i=l;i<=r;i++)
        {
            if(a[i]==c)ans++;
            else a[i]=c;
        }
    }
    else
    {
        if(same[belong[l]]!=-1)pushdown(belong[l]);
        for(int i=l;i<=rig[belong[l]];i++)
        {
            if(a[i]==c)ans++;
            else a[i]=c;
        }
        for(int i=belong[l]+1;i<=belong[r]-1;i++)
        {
            if(same[i]!=-1)
            {
                if(same[i]==c)ans+=block;
                else same[i]=c;
            }
            else//不同
            {
                for(int j=lef[i];j<=rig[i];j++)
                {
                    if(a[j]==c)ans++;
                    else a[j]=c;
                }
                same[i]=c;
            }
        }
        if(same[belong[r]]!=-1)pushdown(belong[r]);
        for(int i=lef[belong[r]];i<=r;i++)
        {
            if(a[i]==c)ans++;
            a[i]=c;
        }
    }
    printf("%d\n",ans);
}
int main()
{
    //freopen("/Users/apple/Desktop/input.txt","r",stdin);
    //freopen("/Users/apple/Desktop/output.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    memset(same,-1,sizeof(same));
    build();
    int l,r,c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d",&l,&r,&c);
        solve(l,r,c);
    }
    return 0;
}
LOJ 6285

题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及询问区间的最小众数。

思路:

解法一:

众数应该是完整的所有块的众数,和不完整块中出现的数。

所以我们可以预处理f(i,j)表示第 i 块到第 j 块的众数(枚举 i 开个桶扫一遍)。

对于块内的众数就用预处理出来的;对于块外的,给每个数 x 开个vector,按顺序存下 x 出现的位置,每次询问 x 时把区间的左右端点放进对应 vector 二分一下即可。

为了不爆内存,所以要先离散化。

我第一次是直接用map映射,每次读取都是mp[a[i]]这样,结果TLE到怀疑人生。看了别人的代码,发现别人的map不是这样用的,而是开了val数组来保存离散后的编号所对应的原来的数字,然后把原数列变成离散后的样子。原来map这么慢啊…涨姿势…(其实还有一种离散的方式来着吧…

解法二:

查询之前,先预处理一下,统计出两个东西:cnt(i)(j)和ans(i)(j)。
cnt(i)(j)表示i这个数在前j块出现的次数;
ans(i)(j)表示从i块到j块的答案,统计的都是整块的答案。
搞定这些的时间复杂度是O(nn√)的。
对于cnt(i)(j)的统计,先做出每一块i出现的次数,然后一遍前缀和搞定。对于ans(i)(j)的统计,先确定起点l,就是第i块的起点,然后r向后暴力移动统计,当r等于一个块的终点时,记录答案。
对于统计ans(i)(j)时,别忘了清数组,看好r……

对于块外的,用前缀和处理,不够的地方暴力统计。

因为这里数据范围有1e5,cnt数组如果在块大为√n时会爆内存(看到最快的用这种方法100000*500居然没爆内存???我以为1e6就要爆内存了???(迷)),或许把块搞得大一点可以?

代码(解法一):

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int n;
int a[100010];
int num;//分块的个数
int belong[100010];//每个数属于哪个分块
int block;//每个块的大小
int lef[400],rig[400];//该块的左/右端点位置
int f[400][400];//第i块到第j块的众数
int ff[400][400];//第i块到第j块的众数出现的次数
map<int,int>mp;//离散数据
vector<int>v[100010];//记录每个数字(离散后)出现的位置
int val[100010];
int apr[100010];//每个数出现的次数(桶排)
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block)num++;
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
    for(int i=1;i<=num;i++)
        lef[i]=(i-1)*block+1,rig[i]=i*block;
    rig[num]=n;//最后一块不满的情况
}
void init()
{
    for(int i=1;i<=num;i++)
    {
        int MAX=0;//出现最多的数字出现的最多次数
        int pos=INF;//出现最多的数字
        memset(apr,0,sizeof(apr));
        for(int j=lef[i];j<=n;j++)
        {
            apr[a[j]]++;
            if(apr[a[j]]>MAX||(apr[a[j]]==MAX&&val[a[j]]<pos))//最小众数
                pos=val[a[j]],MAX=apr[a[j]],
                f[i][belong[j]]=pos,ff[i][belong[j]]=MAX;
        }
    }
    /*for(int i=1;i<=num;i++)
     for(int j=i;j<=num;j++)
     printf("%d~%d:%d %d\n",i,j,f[i][j],ff[i][j]);*/
}
void solve(int l,int r)
{
    int ans=INF,MAX=0;
    if(belong[l]==belong[r])
    {
        for(int i=l;i<=r;i++)
        {
            int d=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(d>MAX||(d==MAX&&val[a[i]]<ans))
                ans=val[a[i]],MAX=d;
        }
    }
    else
    {
        ans=f[belong[l]+1][belong[r]-1];
        MAX=ff[belong[l]+1][belong[r]-1];
        for(int i=l;i<=rig[belong[l]];i++)
        {
            int d=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(d>MAX||(d==MAX&&val[a[i]]<ans))
                ans=val[a[i]],MAX=d;
        }
        for(int i=lef[belong[r]];i<=r;i++)
        {
            int d=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(d>MAX||(d==MAX&&val[a[i]]<ans))
                ans=val[a[i]],MAX=d;
        }
    }
    printf("%d\n",ans);
}
int main()
{
    //freopen("/Users/apple/Desktop/input.txt","r",stdin);
    //freopen("/Users/apple/Desktop/output.txt","w",stdout);
    scanf("%d",&n);
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(mp[a[i]]==0){mp[a[i]]=++cnt;val[cnt]=a[i];}//离散化
        a[i]=mp[a[i]];
        v[a[i]].push_back(i);
    }
    build();
    init();
    int l,r;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&l,&r);
        solve(l,r);
    }
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

补不动的题的一些思想QAQ Previous
POJ2420 爬山算法&&模拟退火 Next