loj6277~6285.分块入门九讲

loj6277.数列分块入门 1

维护序列,支持区间修改,单点查询。

直接分块,修改整块打标记,散块暴力修改。查询直接点值加上所在块的标记即可。

时间复杂度 \(O(n\sqrt n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,block,a[50001],b[50001],tag[50001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
int main()
{
    n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
    }
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read(),x=read();
        if(opt==0)
        {
            int bl=(l-1)/block+1,br=(r-1)/block+1;
            if(bl==br)
            {
                for(register int j=l;j<=r;++j)
                    a[j]+=x;
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                tag[j]+=x;
            for(register int j=l;j<=bl*block;++j)
                a[j]+=x;
            for(register int j=(br-1)*block+1;j<=r;++j)
                a[j]+=x;
        }
        if(opt==1)
            printf("%d\n",a[r]+tag[(r-1)/block+1]);
    }
    return 0;
}

loj6278.数列分块入门 2

维护序列,支持区间加,区间查询小于一个数的个数。

先思考怎样维护答案。可以分块后对每个块维护一个 \(vector\),里面是块内排序后的结果。

修改的时候整块直接打标记,散块暴力重构一遍。

查询整块用 lower_bound,散块暴力查询。

块大小 \(\sqrt n\) 时,复杂度 \(O(n\sqrt n\log n)\),还不够优秀。

考虑调整块大小。设块大小为 \(B\) 发现每次操作的运算次数是 \(B\log n+\frac n B\geq 2\sqrt{n\log n}\),取等条件是 \(B\log n=\frac n B\),解得 \(B=\sqrt{\frac n{\log n}}\),此时复杂度优化为 \(O(n\sqrt{n\log n})\),实测结果比前一种快了一半以上。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,block,a[500001],b[500001],tag[500001];
vector<int> v[500001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void rebuild(int k,int l,int r,int x)
{
    v[k].clear();
    for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
        a[i]+=tag[k];
    for(register int i=l;i<=r;++i)
        a[i]+=x;
    for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
        v[k].push_back(a[i]);
    sort(v[k].begin(),v[k].end());
    tag[k]=0;
}
inline int query(int k,int l,int r,int x)
{
    int res=0;
    for(register int i=l;i<=r;++i)
        res+=(a[i]+tag[k])<x;
    return res;
}
signed main()
{
    n=read();
    block=sqrt(n/log2(n));
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
        v[b[i]].push_back(a[i]);
    }
    for(register int i=1;i<=b[n];++i)
        sort(v[i].begin(),v[i].end());
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read(),x=read();
        int bl=(l-1)/block+1,br=(r-1)/block+1;
        if(opt==0)
        {
            if(bl==br)
            {
                rebuild(bl,l,r,x);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                tag[j]+=x;
            rebuild(bl,l,block*bl,x);
            rebuild(br,block*(br-1)+1,r,x);
        }
        if(opt==1)
        {
            x*=x;
            if(bl==br)
            {
                printf("%lld\n",query(bl,l,r,x));
                continue;
            }
            int ans=0;
            for(register int j=bl+1;j<br;++j)
                ans+=lower_bound(v[j].begin(),v[j].end(),x-tag[j])-v[j].begin();
            printf("%lld\n",ans+query(bl,l,block*bl,x)+query(br,block*(br-1)+1,r,x));
        }
    }
    return 0;
}

loj6279.数列分块入门 3

维护序列,支持区间加,区间查询前驱。

和上一题完全没区别,只是改一下查询的东西即可。

但是这一题为啥块长 \(\sqrt n\) 更快了啊qq_emoji: xia感觉好神秘。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,a[100001],b[100001],block,tag[100001];
vector<int> v[100001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void rebuild(int k,int l,int r,int x)
{
    v[k].clear();
    for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
        a[i]+=tag[k];
    for(register int i=l;i<=r;++i)  
        a[i]+=x;
    for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
        v[k].push_back(a[i]);
    tag[k]=0;
    sort(v[k].begin(),v[k].end());
}
inline int query(int k,int l,int r,int x)
{
    int res=-1ll<<60;
    for(register int i=l;i<=r;++i)
        if(a[i]+tag[k]<x&&a[i]+tag[k]>res)
            res=a[i]+tag[k];
    return res;
}
signed main()
{
    n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
        v[b[i]].push_back(a[i]);
    }
    for(register int i=1;i<=b[n];++i)
        sort(v[i].begin(),v[i].end());
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read(),x=read();
        int bl=(l-1)/block+1,br=(r-1)/block+1;
        if(opt==0)
        {
            if(bl==br)
            {
                rebuild(bl,l,r,x);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                tag[j]+=x;
            rebuild(bl,l,bl*block,x);
            rebuild(br,(br-1)*block+1,r,x);
        }
        if(opt==1)
        {
            int ans=-1ll<<60;
            if(bl==br)
            {
                ans=query(bl,l,r,x);
                printf("%lld\n",ans==-1ll<<60? -1:ans);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
            {
                int pos=lower_bound(v[j].begin(),v[j].end(),x-tag[j])-v[j].begin()-1;
                if(pos==-1)
                    continue;
                if(v[j][pos]+tag[j]>ans)
                    ans=v[j][pos]+tag[j];
            }
            ans=max(ans,max(query(bl,l,bl*block,x),query(br,(br-1)*block+1,r,x)));
            printf("%lld\n",ans==-1ll<<60? -1:ans);
        }
    }
    return 0;
}

loj6280.数列分块入门 4

维护序列,支持区间加,区间求和。

在第一题的基础上多维护一个整块的和就好了。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define int long long
int n,block,a[50001],b[50001],tag[50001],sum[50001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
signed main()
{
    n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
        sum[b[i]]+=a[i];
    }
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read(),x=read();
        int bl=(l-1)/block+1,br=(r-1)/block+1;
        if(opt==0)
        {
            if(bl==br)
            {
                for(register int j=l;j<=r;++j)
                    a[j]+=x;
                sum[bl]+=x*(r-l+1);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
            {
                tag[j]+=x;
                sum[j]+=x*block;
            }
            for(register int j=l;j<=bl*block;++j)
            {
                a[j]+=x;
                sum[bl]+=x;
            }
            for(register int j=(br-1)*block+1;j<=r;++j)
            {
                a[j]+=x;
                sum[br]+=x;
            }
        }
        if(opt==1)
        {
            int ans=0;
            ++x;
            if(bl==br)
            {
                for(register int j=l;j<=r;++j)
                    ans=(ans+(a[j]+tag[bl])%x)%x;
                printf("%lld\n",ans);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                ans=(ans+sum[j]%x)%x;
            for(register int j=l;j<=bl*block;++j)
                ans=(ans+(a[j]+tag[bl])%x)%x;
            for(register int j=(br-1)*block+1;j<=r;++j)
                ans=(ans+(a[j]+tag[br])%x)%x;
            printf("%lld\n",ans);
        }
    }
    return 0;
}

loj6281.数列分块入门 5

维护数列,支持区间开方,区间求和。

经典套路就是开方开不了几次就到 1 了,然后就不用处理了,直接维护整块最大值和整块和即可。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define int long long
int n,block,a[50001],b[50001],maxn[50001],sum[50001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void rebuild(int k,int l,int r)
{
    for(register int i=l;i<=r;++i)
        a[i]=sqrt(a[i]);
    sum[k]=maxn[k]=0;
    for(register int i=(k-1)*block+1;i<=min(k*block,n);++i)
    {
        sum[k]+=a[i];
        maxn[k]=max(maxn[k],a[i]);
    }
}
signed main()
{
    n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
        maxn[b[i]]=max(maxn[b[i]],a[i]);
        sum[b[i]]+=a[i];
    }
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read();
        read();
        int bl=(l-1)/block+1,br=(r-1)/block+1;
        if(opt==0)
        {
            if(bl==br)
            {
                rebuild(bl,l,r);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                if(maxn[j]>1)
                    rebuild(j,(j-1)*block+1,j*block);
            rebuild(bl,l,bl*block);
            rebuild(br,(br-1)*block+1,r);
        }
        if(opt==1)
        {
            int ans=0;
            if(bl==br)
            {
                for(register int j=l;j<=r;++j)
                    ans+=a[j];
                printf("%lld\n",ans);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                ans+=sum[j];
            for(register int j=l;j<=bl*block;++j)
                ans+=a[j];
            for(register int j=(br-1)*block+1;j<=r;++j)
                ans+=a[j];
            printf("%lld\n",ans);
        }
    }
    return 0;
}

loj6282.数列分块入门 6

维护序列,支持插入值,查询某个位置的值。

进行分块,每个块用 vector 存块内序列,插入暴力跳找到所在的块然后直接插入,查询同理。

如果插入之后发现这个块大小太大就暴力重构整个序列。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
int n,m,block,b[200001],maxn;
vector<int> v[200001],tmp;
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void rebuild()
{
    for(register int i=1;i<=maxn;++i)
    {
        tmp.insert(tmp.end(),v[i].begin(),v[i].end());
        v[i].clear();
    }
    block=sqrt(m);
    maxn=(m-1)/block+1;
    for(register int i=1;i<=m;++i)
        v[(i-1)/block+1].push_back(tmp[i-1]);
}
inline pair<int,int> getblock(int x)
{
    for(register int i=1;i<=maxn;++i)
    {
        if(x<=v[i].size())
            return make_pair(i,x-1);
        x-=v[i].size();
    }
}
int main()
{
    m=n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        b[i]=(i-1)/block+1;
        v[b[i]].push_back(read());
    }
    maxn=b[n];
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read();
        read();
        if(opt==0)
        {
            pair<int,int> k=getblock(l);
            v[k.first].insert(v[k.first].begin()+k.second,r);
            ++m;
            if(v[k.first].size()>block*20)
                rebuild();
        }
        if(opt==1)
        {
            pair<int,int> k=getblock(r);
            printf("%d\n",v[k.first][k.second]);
        }
    }
    return 0;
}

loj6283.数列分块入门 7

维护数列,支持区间加、区间乘、单点查询。

没啥好说的,在第一题的基础上多维护一个乘法标记,然后每次散块直接暴力重构下放标记即可。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int mod=10007;
int n,block,a[100001],b[100001],tag[100001][2];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void rebuild(int k,int l,int r,int x,bool flag)
{
    for(register int i=(k-1)*block+1;i<=min(k*block,n);++i)
        a[i]=(a[i]*tag[k][1]%mod+tag[k][0])%mod;
    tag[k][0]=0;
    tag[k][1]=1;
    for(register int i=l;i<=r;++i)
        a[i]=(flag? a[i]*x%mod:(a[i]+x)%mod);
}
int main()
{
    n=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i)
    {
        a[i]=read()%mod;
        b[i]=(i-1)/block+1;
    }
    for(register int i=1;i<=b[n];++i)
        tag[i][1]=1;
    for(register int i=1;i<=n;++i)
    {
        int opt=read(),l=read(),r=read(),x=read()%mod;
        int bl=(l-1)/block+1,br=(r-1)/block+1;
        if(opt==0)
        {
            if(bl==br)
            {
                rebuild(bl,l,r,x,0);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
                tag[j][0]=(tag[j][0]+x)%mod;
            rebuild(bl,l,bl*block,x,0);
            rebuild(br,(br-1)*block+1,r,x,0);
        }
        if(opt==1)
        {
            if(bl==br)
            {
                rebuild(bl,l,r,x,1);
                continue;
            }
            for(register int j=bl+1;j<br;++j)
            {
                tag[j][1]=tag[j][1]*x%mod;
                tag[j][0]=tag[j][0]*x%mod;
            }
            rebuild(bl,l,bl*block,x,1);
            rebuild(br,(br-1)*block+1,r,x,1);
        }
        if(opt==2)
            printf("%d\n",(a[r]*tag[br][1]%mod+tag[br][0])%mod);
    }
    return 0;
}

loj6284.数列分块入门 8

维护序列,支持区间查询一个数的数量,区间推平。

还是维护 vector,整块打标记,散块暴力重构。

查询的时候有标记看标记,没有标记 upper_bound 减掉 lower_bound 就是贡献。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,block,a[100001],b[100001],tag[100001];
vector<int> v[100001];
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline int rebuild(int k,int l,int r,int x)
{
    int res=0;
    v[k].clear();
    if(tag[k]!=1ll<<60)
        for(register int i=(k-1)*block+1;i<=min(k*block,n);++i)
            a[i]=tag[k];
    tag[k]=1ll<<60;
    for(register int i=l;i<=r;++i)
    {
        res+=a[i]==x;
        a[i]=x;
    }
    for(register int i=(k-1)*block+1;i<=min(k*block,n);++i)
        v[k].push_back(a[i]);
    sort(v[k].begin(),v[k].end());
    return res;
}
signed main()
{
    n=read();
    block=sqrt(n/log2(n));
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        b[i]=(i-1)/block+1;
        v[b[i]].push_back(a[i]);
    }
    for(register int i=1;i<=b[n];++i)
    {
        sort(v[i].begin(),v[i].end());
        tag[i]=1ll<<60;
    }
    for(register int i=1;i<=n;++i)
    {
        int l=read(),r=read(),x=read();
        int bl=b[l],br=b[r];
        if(bl==br)
        {
            printf("%lld\n",rebuild(bl,l,r,x));
            continue;
        }
        int ans=0;
        for(register int j=bl+1;j<br;++j)
        {
            if(tag[j]!=1ll<<60)
                ans+=tag[j]==x? block:0;
            else
                ans+=upper_bound(v[j].begin(),v[j].end(),x)-lower_bound(v[j].begin(),v[j].end(),x);
            tag[j]=x;
        }
        ans+=rebuild(bl,l,bl*block,x)+rebuild(br,(br-1)*block+1,r,x);
        printf("%lld\n",ans);
    }
    return 0;
}

loj6285.数列分块入门 9

维护序列,支持区间最小众数。

直接分块,预处理 \(f_{l,r}\) 表示块 \(l\) 到块 \(r\) 的最小众数。查询的时候直接拿出整块的众数,连同散块一共 \(O(\sqrt n)\) 个数一一判断即可。

复杂度 \(O(n\sqrt{n\log n})\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,block,a[100001],b[100001],f[2001][2001],node[100001],cnt,sum[100001];
vector<int> v[100001],tmp;
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline int solve(int n,int l,int r)
{
    int num=cnt+1,maxn=-1<<30;
    for(auto i:tmp)
    {
        int val=upper_bound(v[i].begin(),v[i].end(),r)-lower_bound(v[i].begin(),v[i].end(),l);
        if(val==maxn)
            num=min(num,i);
        if(val>maxn)
        {
            maxn=val;
            num=i;
        }
    }
    return node[num];
}
signed main()
{
    n=read();
    block=sqrt(n/log2(n));
    for(register int i=1;i<=n;++i)
    {
        node[i]=a[i]=read();
        b[i]=(i-1)/block+1;
    }
    sort(node+1,node+n+1);
    cnt=unique(node+1,node+n+1)-node-1;
    for(register int i=1;i<=n;++i)
    {
        a[i]=lower_bound(node+1,node+cnt+1,a[i])-node;
        v[a[i]].push_back(i);
    }
    for(register int i=1;i<=b[n];++i)
    {
        for(register int j=1;j<=cnt;++j)
            sum[j]=0;
        int num=cnt+1,maxn=-1<<30;
        for(register int j=i;j<=b[n];++j)
        {
            for(register int k=(j-1)*block+1;k<=min(j*block,n);++k)
            {
                ++sum[a[k]];
                if(sum[a[k]]==maxn)
                    num=min(num,a[k]);
                if(sum[a[k]]>maxn)
                {
                    maxn=sum[a[k]];
                    num=a[k];
                }
            }
            f[i][j]=num;
        }
    }
    for(register int i=1;i<=n;++i)
    {
        int l=read(),r=read();
        int bl=b[l],br=b[r];
        tmp.clear();
        if(bl==br)
        {
            for(register int j=l;j<=r;++j)
            {
                tmp.push_back(a[j]);
                sort(tmp.begin(),tmp.end());    
            }
            printf("%lld\n",solve(i,l,r));
            continue;
        }
        if(bl+1<=br-1)
            tmp.push_back(f[bl+1][br-1]);
        for(register int j=l;j<=bl*block;++j)
            tmp.push_back(a[j]);
        for(register int j=(br-1)*block+1;j<=r;++j)
            tmp.push_back(a[j]);
        sort(tmp.begin(),tmp.end());
        printf("%lld\n",solve(i,l,r));
    }
    return 0;
}

完结撒花

posted @ 2021-10-01 17:51  绝顶我为峰  阅读(278)  评论(2编辑  收藏  举报