分块大法吼(分块初步)

分块大法吼(分块初步)


什么是分块:

  (就是乱搞)

  我们考虑一个问题:区间修改单点查询,n,m<=1e5;

  那么我们可以怎么解决这个问题呢?

  线段树!树状数组!

 

  分块~!

  分块是何物呢:是一种基于暴力的算法(优雅的暴力嗷)

  我们考虑如下一种玄学方法:

  将整个序列分为几大块,维护每个大块的和,单点修改显然可以O(1)实现;

  那么区间查询怎么办呢?假设查询区间是[L,R],那么可以肯定的是,如果[L,R]内包含大块,我们只要加上大块的和就可以惹,至于不在大块内的数,我们可以暴力求和嘛qwq;

  虽然这样听起来也会令你T到升天,但是总是比n^2快了一些对吧;

  那么这么做的时间复杂度到底是多少呢?我们来分析一下;

  首先,假设我们把整个序列长度为N,分成了M块,那么每次查询的复杂度为:O(M+N/M);

  这个复杂度到底在什么范围内呢?

  我们根据均值(基本)不等式可以得出(M+N/M)<=2√(N);在M==N/M时取到最小值;

  也就是嗦,当M=√N时,总复杂度最小,为√N;

  那么解决问题总复杂就是O(N+M√N)的,怎么样,是不是还可以接受;


 

相比与其他数据结构优势:

  我们来看另一个问题:

  区间加,区间小于k的个数,n,m<=1e5;

  线段树玩家已暴毙

  我们仍然可以用分块来求解:

  先将每个块进行块内排序,复杂度nlog√n;

  区间小于k个个数求法就很显然了:对于不在整块内的部分,暴力统计;整块内的部分二分查找,每次处理复杂度√Nlog√N;

  区间加法怎么办呢?

  对于整块,区间加法显然不破坏大小关系,直接打上一个标记即可;

  对于散块,由于散块最多有2块,显然我们可以直接打破,暴力重构,重新排序,复杂度√Nlog√N;

  总复杂度O(Nlog√N+M√Nlog√N);

  发现了吗,由于分块算法牺牲了部分时间复杂度,所以可以处理的信息更加灵活,相比与传统区间数据结构,分块算法可以处理无法快速区间信息合并的信息;

 


 

例题:

  分块入门1——9

  这里借用黄学长的分块入门系列qwq;

  分块入门1:

  区间加法,单点查询;

  上面讲过惹qwq;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010];
int bel[100010],tag[100010];
inline void update(int l,int r,int k)
{
    int lx=bel[l],rx=bel[r];
    for(int i=l;bel[i]==lx&&i<=r;++i)
        a[i]+=k;
    if(lx==rx) return ;
    for(int i=r;bel[i]==rx;--i)
        a[i]+=k;
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld\n",a[y]+tag[bel[y]]);
    }
return 0;
}
View Code

 

 

 

 

  分块入门2:

  区间加法,区间小于k个数;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt,sum;
int a[100010],bel[100010],tag[100010];
int c[500][500];
inline void sor(int x)
{
    memset(c[x],0,sizeof(c[x]));
    for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i)
    {
        c[x][++c[x][0]]=a[i];
    }
    sort(c[x]+1,c[x]+c[x][0]+1);
}
inline void update(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    for(int i=x;bel[i]==lx&&i<=y;++i)
        a[i]+=k;
    sor(lx);
    if(lx==rx) return ;
    for(int i=y;bel[i]==rx;--i)
        a[i]+=k;
    sor(rx);
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
inline int query(int x,int y,int k)
{
    int res=0;
    int lx=bel[x],rx=bel[y];
    for(int i=x;bel[i]==lx&&i<=y;++i)
    {
        if(a[i]+tag[lx]<k) ++res;
    }
    if(lx==rx) return res;
    for(int i=y;bel[i]==rx;--i)
    {
        if(a[i]+tag[rx]<k) ++res;
    }
    
    for(int i=lx+1;i<rx;++i)
    {
        int l=1,r=c[i][0],ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(c[i][mid]+tag[i]>=k) r=mid-1;
            else ans=mid,l=mid+1;
        }
        res+=ans;
    }
    
    return res;
}
signed main()
{
    n=read();
    cnt=sqrt(n); 
    sum=n/cnt+(n%cnt>0);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        c[bel[i]][++c[bel[i]][0]]=a[i];
    }
    for(int i=1;i<=sum;++i)
    {
        sort(c[i]+1,c[i]+c[i][0]+1);
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld\n",query(x,y,z*z));
    }
return 0;
}
View Code

 

  

  分块入门3:

  区间加法,区间小于k前驱;

  显然可以沿用上一题的方法,块内排序+二分;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt,sum;
int a[100010],bel[100010],tag[100010];
int c[500][500];
inline void sor(int x)
{
    memset(c[x],0,sizeof(c[x]));
    for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i)
        c[x][++c[x][0]]=a[i];
    sort(c[x]+1,c[x]+c[x][0]+1);
}
inline void update(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    for(int i=x;i<=y&&bel[i]==lx;++i)
        a[i]+=k;
    sor(lx);
    if(lx==rx) return ;
    for(int i=y;bel[i]==rx;--i)
        a[i]+=k;
    sor(rx);
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
inline int query(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    int ans=-1;
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        if(a[i]+tag[lx]<k) ans=max(ans,a[i]+tag[lx]);
    }
    if(lx==rx) return ans;
    for(int i=y;bel[i]==rx;--i)
    {
        if(a[i]+tag[rx]<k) ans=max(ans,a[i]+tag[rx]);
    }
    for(int i=lx+1;i<rx;++i)
    {
        int l=1,r=c[i][0];
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(c[i][mid]+tag[i]<k) ans=max(ans,c[i][mid]+tag[i]),l=mid+1;
            else r=mid-1;
        }
    }
    return ans;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    sum=n/cnt+(n%cnt>0);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        c[bel[i]][++c[bel[i]][0]]=a[i];
    }
    for(int i=1;i<=sum;++i) sort(c[i]+1,c[i]+c[i][0]+1);
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld\n",query(x,y,z));
    }
return 0;
}
View Code

 

 

  分块入门4:

  区间加法,区间求和;

  区间求和已经讲过了,至于区间加法,在整块上打上标记表示这一块加上了多少,散块直接暴力添加,修改块和;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010],bel[100010],tag[100010];
int sum[100010];
inline void update(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    for(int i=x;i<=y&&bel[i]==lx;++i)
        a[i]+=k,sum[lx]+=k;    
    if(lx==rx) return ;
    for(int i=y;bel[i]==rx;--i)
        a[i]+=k,sum[rx]+=k;
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
inline int query(int x,int y,int lyy)
{
    int lx=bel[x],rx=bel[y],res=0;
    for(int i=x;i<=y&&bel[i]==lx;++i)
        res = ( res + a[i] + tag[lx] ) % lyy ;
    if(lx==rx) return res%lyy;
    for(int i=y;bel[i]==rx;--i)
        res = ( res + a[i] + tag[rx] ) % lyy ;
    for(int i=lx+1;i<rx;++i)
        res = ( res + sum[i] + ( cnt * tag[i] ) % lyy ) % lyy ; 
    return res%lyy;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        sum[bel[i]]+=a[i];
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld\n",query(x,y,z+1));
    }
return 0;
}
View Code

 

 

  分块入门5:

  区间求和,区间开方(下取整);

  啥啥啥这是啥???区间开方?这是个啥?(我刚看到这个题目内心OS)

  不过仔细思考,开方还要下取整,那岂不是1和0怎么开不变咯;

  分析每个数据小于2^31-1,那么一个数最多被开方5次,所以我们每次只要在对一个区间进行暴力开方后,判断这个区间是否全部变成了0or1,如果是,打上一个标记下次跳过;

  由于每个数只能被开方5次,那么总复杂度O(5*N+M√N);

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010],bel[100010],tag[100010];
int sum[100010];
inline void sqr(int x)
{
    bool flag=0;
    for(int i=(x-1)*cnt+1;i<=min(n,cnt*x);++i)
    {
        sum[x]-=a[i];
        sum[x]+=(int)sqrt(a[i]);
        a[i]=sqrt(a[i]);
        if(a[i]>1) flag=1;
    }
    if(!flag) tag[x]=1;
}
inline void update(int x,int y)
{
    int lx=bel[x],rx=bel[y];
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        sum[lx]-=a[i];
        sum[lx]+=(int)sqrt(a[i]);
        a[i]=sqrt(a[i]);
    }
    if(lx==rx) return ;
    for(int i=y;bel[i]==rx;--i)
    {
        sum[rx]-=a[i];
        sum[rx]+=(int)sqrt(a[i]);
        a[i]=sqrt(a[i]);
    }
    for(int i=lx+1;i<rx;++i)
    {
        if(tag[i]) continue;
        else sqr(i);
    }
}
inline int query(int x,int y)
{
    int lx=bel[x],rx=bel[y];
    int res=0;
    for(int i=x;i<=y&&bel[i]==lx;++i)
        res+=a[i];
    if(lx==rx) return res;
    for(int i=y;bel[i]==rx;--i)
        res+=a[i];
    for(int i=lx+1;i<rx;++i)
        res+=sum[i];
    return res;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        sum[bel[i]]+=a[i];
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y);
        else
            printf("%lld\n",query(x,y));
    }
return 0;
}
View Code

 

 

  分块入门6:

  单点插入单点查询,数据随机;

  考虑到数据随机,统计块内数字数量,用链表插入即可;

  但是有一个问题:数据不随机怎么办?如果每次都往同一个块内插入,复杂度无法保证;

  我们可以设定一个值,(我设为当前序列数量的根号),一旦插入次数超过这个值,将整个序列重新分块,理论复杂度O(M√N+N√M);

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt,tot;
int a[200010],bel[200010],st[200010],ed[200010];
int sum[200010];
int nxt[200010],pre[200010];
inline void miao()
{
    int t=0;
    memset(sum,0,sizeof(sum));
    cnt=sqrt(tot);
    for(int i=st[1];i;i=nxt[i])
    {
        bel[i]=t/cnt+1;
        ++sum[bel[i]];
        if(bel[i]^bel[pre[i]]) st[bel[i]]=i,ed[bel[i]-1]=pre[i];
        ++t;
    }
}
inline void update(int x,int y)
{
    int pos=0,now;
    for(int i=1;;++i)
    {
        if(pos+sum[i]>=x)
        {
            now=i;
            break;
        }
        pos+=sum[i];
    }
    for(int i=st[now];i!=nxt[ed[now]];i=nxt[i])
    {
        if(++pos==x)
        {
            a[++tot]=y;
            nxt[tot]=i;
            pre[tot]=pre[i];
            nxt[pre[i]]=tot;
            pre[i]=tot;
            if(i==st[now])    st[now]=tot;
            ++sum[now];
            break;
        }
    }
}
inline int query(int x)
{
    int pos=0,now;
    for(int i=1;;++i)
    {
        if(pos+sum[i]>=x)
        {
            now=i;
            break;
        }
        pos+=sum[i];
    }
    for(int i=st[now];i!=nxt[ed[now]];i=nxt[i])
    {
        if(++pos==x)
            return a[i];
    }
}
signed main()
{
    n=tot=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        if(bel[i]^bel[i-1]) st[bel[i]]=i,ed[bel[i]-1]=i-1;
        ++sum[bel[i]];
        if(i^n) nxt[i]=i+1;
        if(i^1) pre[i]=i-1;
        if(i==n) ed[bel[i]]=i;
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
        {
            update(x,y);
            if(tot%cnt==0) miao();
        }
        else
            printf("%lld\n",query(y));
    }
return 0;
}
View Code

 

  分块入门7:

  区间乘法,区间加法,区间求和;(就是线段树2嘛)

  考虑两种标记的先后顺序:

  如果我们对于两个同时存在的标记,先加再乘:

  假设当前状态为(a+add)*mul;又添加了一组[add2,mul2]标记;

  当前状态变为((a+add)*mul+add2)*mul2;

  展开((a+add)*mul*mul2+add2*mul2);

  展开(a*mul*mul2+add*mul*mul2+add2*mul2);

  恢复标记格式:(a+add+(add2/mul))*mul*mul2;

  发现了什么?分数!这样可能会丢精度qwq;

  改变顺序,先乘后加;

  (a*mul+add)添加新标记[add2,mul2];

  当前状态变为((a*mul)+add)*mul2+add2;

  展开 (a*mul*mul2+add*mul2+add2);

  恢复标记格式:(a*mul*mul2)+add*mul2+add2;

  完美;

  注意细节:在分块中,如果要对散块处理,要先把整个散块的标记全部下放;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
const int chx=10007;
int n,cnt;
int a[100010],bel[100010];
int add[100010],mul[100010];
inline void push_down(int x)
{
    for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i)
    {
        a[i] = ( a[i] * mul[x] + add[x] ) % chx ; 
    }
    add[x]=0;
    mul[x]=1;
}
inline void upadd(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    push_down(lx);
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        a[i] = ( a[i] + k ) % chx ;
    }
    if(lx==rx) return ;
    push_down(rx);
    for(int i=y;bel[i]==rx;--i)
    {
        a[i] = ( a[i] + k ) % chx ;
    }
    for(int i=lx+1;i<rx;++i)
        add[i] = ( add[i] + k ) % chx ;
}
inline void upmul(int x,int y,int k)
{
    int lx=bel[x],rx=bel[y];
    push_down(lx);
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        a[i] = ( a[i] * k ) % chx ;
    }
    if(lx==rx) return ;
    push_down(rx);
    for(int i=y;bel[i]==rx;--i)
    {
        a[i] = ( a[i] * k ) % chx ;
    }
    for(int i=lx+1;i<rx;++i)
    {
        add[i] = ( add[i] * k ) % chx ;
        mul[i] = ( mul[i] * k ) % chx ; 
    }
}
inline int query(int x)
{
    return (( a[x] * mul[bel[x]] ) % chx + add[bel[x]] ) % chx ;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read()%chx;
        bel[i]=(i-1)/cnt+1;
        mul[bel[i]]=1;
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
        {
            upadd(x,y,z);
        }
        if(t==1)
        {
            upmul(x,y,z);
        }
        if(t==2)
        {
            printf("%lld\n",query(y));
        }
    }
return 0;
}
View Code

 

  分块入门8:

  查询一段区间内元素等于k的个数,并将该区间内元素全部改为k;

  一个想法是,将整块的全部为同一数字的打上标记,非同一数字:散块暴力,整块块内排序二分查找;

  很不幸,他T了……

  正解我看了想喷人qwq

  对于非同一数字部分暴力统计,同一数字部分整块处理……

  复杂度分析:对于原序列的改造:只要用O(n)的复杂度,一定会将原序列全部变成同一数字的;

  那么处理不同数字的代价是什么呢?

  考虑每一次操作,最多将两个块变成不同数字的,这两个块是需要暴力的,也就是说每次操作只会产生2个不同数字的块,带来√N的复杂度;

  总复杂度为O(M√N+N);

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010],bel[100010],tag[100010];
inline void push_down(int x)
{
    for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i)
        a[i]=tag[x];
    tag[x]=-1;
}
inline int query(int x,int y,int k)
{
    int sum=0;
    int lx=bel[x],rx=bel[y];
    if(tag[lx]!=-1) push_down(lx);
    for(int i=x;i<=y&&bel[i]==lx;++i)
        sum+=(a[i]==k),a[i]=k;
    if(lx==rx) return sum;
    if(tag[rx]!=-1) push_down(rx);
    for(int i=y;bel[i]==rx;--i)
        sum+=(a[i]==k),a[i]=k;
    for(int i=lx+1;i<rx;++i)
    {
        if(tag[i]!=-1)
        {
            sum+=(tag[i]==k?cnt:0);
        }
        else
        {
            for(int j=(i-1)*cnt+1;j<=i*cnt;++j)
                sum+=(a[j]==k);
        } 
        tag[i]=k;
    }
    return sum;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        tag[i]=-1;
    }
    for(int x,y,z,i=1;i<=n;++i)
    {
        x=read(),y=read(),z=read();
        printf("%lld\n",query(x,y,z));
    }
return 0;
}
View Code

 

 

  分块入门9&&洛谷P4168蒲公英:

  区间众数查询;

  毒瘤来惹QAQ这道题目分享两个方法:

  方法一:二分

  说点别的,明明最近有一次考试题目就是这道题的阉割版,只取二分部分就是切掉,我居然没想出来,哭惹哭惹;

  有一个引理:有两个数字集合A和B,设A集合的众数为X,那么整个集合的众数一定属于X∪B;

  证明:不会,详见陈立杰《区间众数解题报告》;

  根据引理,我们可以知道:只要判断两个散块的数和中间整块的众数就可以找到众数;

  枚举散块和整块众数复杂度为O(N),那么我们需要一个O(1)判断一个数字在区间中出现次数的方法;

  很遗憾,没有,但是有logN的;

  我们可以如下处理:

  用vector数组存储每个数字出现的位置,用lower_bound和upper_bound找到这个数字在区间中第一次出现的位置和最后一次出现的位置;

  至于整块的众数,可以直接N√N处理出来,详见代码,一看就会qwq;

  注意一个问题:在该题目中,由于预处理只要一次,而查询代价较多,我们可以适当减小块的大小,让每次查询的代价更小;

  亲测当块的大小为50的时候跑地飞快;下面放的是洛谷AC代码,LOJ上的题目需略作修改;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,m,cnt,tot,kuai,miao;
int lx,rx,maxsum,ans,t;
int a[100010],bel[100010];
int f[1010][1010];
int val[100010];
int sum[100010];
vector<int> q[100010]; 
map<int,int> vis;
inline void get_num(int x)
{
    memset(sum,0,sizeof(sum));
    int maxsum=-1,ans=0;
    for(int i=(x-1)*cnt+1;i<=n;++i)
    {
        ++sum[a[i]];
        if(sum[a[i]]>maxsum||(sum[a[i]]>=maxsum&&val[a[i]]<=val[ans]))
            maxsum=sum[a[i]],ans=a[i];
        f[x][bel[i]]=ans;
    }
}
inline int ask(int l,int r,int k)
{
    return upper_bound(q[k].begin(),q[k].end(),r)-lower_bound(q[k].begin(),q[k].end(),l);
}
inline int query(int x,int y)
{
    lx=bel[x],rx=bel[y];
    maxsum=0,ans=0;
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        t=ask(x,y,a[i]);
        if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans]))
            maxsum=t,ans=a[i];
    }
    if(lx^rx)
    {
        for(int i=y;bel[i]==rx;--i)
        {    
            t=ask(x,y,a[i]);
            if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans]))
                maxsum=t,ans=a[i];
        }
        if(lx+1<=rx-1)
        {
            t=ask(x,y,f[lx+1][rx-1]);
            if(t>maxsum||(t>=maxsum&&val[f[lx+1][rx-1]]<=val[ans]))
                maxsum=t,ans=f[lx+1][rx-1];
        }
    }
    return ans;
}
signed main()
{
    n=read(),m=read();
    cnt=50;
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        if(!vis[a[i]])
        {
            vis[a[i]]=++tot;
            val[tot]=a[i];
        }
        a[i]=vis[a[i]];
        q[a[i]].push_back(i);
        bel[i]=(i-1)/cnt+1;
    }
    for(int i=1;i<=bel[n];++i)
        get_num(i);
    for(int x,y,i=1;i<=m;++i)
    {
        x=read(),y=read();
        x=(x+miao-1)%n+1,y=(y+miao-1)%n+1;
        if(x>y) swap(x,y);
        miao=val[query(x,y)];
        printf("%lld\n",miao);
    }
return 0;
}
View Code

  方法二:预处理:

  预处理出一个p[ ][ ]数组,p[ i ][ j ]表示前i个块内,j这个数字出现的次数;

  表示这个方法也应该是N√N才对,甚至理论复杂度更优秀,但是由于一股神秘力量导致方法一将块大小改为50后快得飞起,这个方法很不幸地慢了一截;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,m,cnt,tot,kuai,miao;
int lx,rx,maxsum,ans,t;
int a[100010],bel[100010];
int f[1010][1010];
int p[500][40010];
int val[100010];
int sum[100010];
int c[100010];
inline void get_num(int x)
{
    memset(sum,0,sizeof(sum));
    int maxsum=-1,ans=0;
    for(int i=(x-1)*cnt+1;i<=n;++i)
    {
        ++sum[a[i]];
        if(sum[a[i]]>maxsum||(sum[a[i]]>=maxsum&&val[a[i]]<=val[ans]))
            maxsum=sum[a[i]],ans=a[i];
        f[x][bel[i]]=ans;
    }
}
inline int query(int x,int y)
{
    lx=bel[x],rx=bel[y];
    maxsum=0,ans=0;
    memset(sum,0,sizeof(sum));
    for(int i=x;i<=y&&bel[i]==lx;++i)
    {
        ++sum[a[i]];
        t=sum[a[i]]+max(p[rx-1][a[i]]-p[lx][a[i]],0ll);
        if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans]))
            maxsum=t,ans=a[i];
    }
    if(lx^rx)
    {
        for(int i=y;bel[i]==rx;--i)
        {    
            ++sum[a[i]];
            t=sum[a[i]]+max(p[rx-1][a[i]]-p[lx][a[i]],0ll);
            if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans]))
                maxsum=t,ans=a[i];
        }
        if(lx+1<=rx-1)
        {
            t=p[rx-1][f[lx+1][rx-1]]-p[lx][f[lx+1][rx-1]]+sum[f[lx+1][rx-1]];
            if(t>maxsum||(t>=maxsum&&val[f[lx+1][rx-1]]<=val[ans]))
                maxsum=t,ans=f[lx+1][rx-1];
        }
    }
    return ans;
}
signed main()
{
    n=read(),m=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
        c[++c[0]]=a[i];
    }
    sort(c+1,c+c[0]+1);
    c[0]=unique(c+1,c+c[0]+1)-c-1;
    for(int i=1;i<=n;++i)
    {
        int tyx=lower_bound(c+1,c+c[0]+1,a[i])-c;
        val[tyx]=a[i];
        a[i]=tyx;
    }
    for(int i=1;i<=bel[n];++i)
    {
        get_num(i);
        for(int j=(i-1)*cnt+1;j<=min(n,i*cnt);++j)
        {
            ++p[i][a[j]];
        }
        for(int j=1;j<=c[0];++j)
        {
            p[i][j]+=p[i-1][j];
        }
    }
    for(int x,y,i=1;i<=m;++i)
    {
        x=read(),y=read();
        x=(x+miao-1)%n+1,y=(y+miao-1)%n+1;
        if(x>y) swap(x,y);
        miao=val[query(x,y)];
        printf("%lld\n",miao);
    }
return 0;
}
View Code

 

转载于:https://www.cnblogs.com/knife-rose/p/11299303.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值