分塊大法吼(分塊初步)

分塊大法吼(分塊初步)


什麼是分塊:

  (就是亂搞)c++

  咱們考慮一個問題:區間修改單點查詢,n,m<=1e5;
算法

  那麼咱們能夠怎麼解決這個問題呢?數組

  線段樹!樹狀數組!數據結構

 

  分塊~!ide

  分塊是何物呢:是一種基於暴力的算法(優雅的暴力嗷)spa

  咱們考慮以下一種玄學方法:3d

  將整個序列分爲幾大塊,維護每一個大塊的和,單點修改顯然能夠O(1)實現;code

  那麼區間查詢怎麼辦呢?假設查詢區間是[L,R],那麼能夠確定的是,若是[L,R]內包含大塊,咱們只要加上大塊的和就能夠惹,至於不在大塊內的數,咱們能夠暴力求和嘛qwq;blog

  雖然這樣聽起來也會令你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
相關文章
相關標籤/搜索