分块 蒲公英 洛谷P4168 求区间众数(强制在线)

洛谷P4168 题意:求区间l到r的众数 强制在线
思路:分块 考虑维护什么:
对于整块 希望直接得到众数及其出现的次数  
对于零散处 暴力求出每个数出现次数 但还要知道它们在整块中的出现次数 来更新答案
维护两个要预处理的数组:ans表示块与块之间的众数是什么 sum表示某个数在某一块之前出现的次数
O(n*sqrt(n))预处理
细节:要离散化 用桶来记录比较出现次数 要记得还原

#include<bits/stdc++.h>
using namespace std;
#define N 40005
int num[N],pos[N],t[N],sum[N][205],a[N],b[N],ans[205][205],sta[205],end[205];
int solve(int l,int r)
{
    int p=pos[l],q=pos[r];
    if(p==q){//如果在同一个块中 就暴力for 
        int now=0;
        for(int i=l;i<=r;i++){
            t[num[i]]++;
            if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i];
        }
        for(int i=l;i<=r;i++) t[num[i]]=0;//每次记得还原 
        return b[now];
    }//for(int i=0;i<=6;i++) printf("t:%d ",t[i]);
    int now=ans[p+1][q-1];//printf("la: %d
",now);
    t[now]+=sum[now][q-1]-sum[now][p];//初始化整块之间的众数及其出现次数 
    for(int i=l;i<=end[p];i++){//同样用桶去比较 
        if(!t[num[i]]) t[num[i]]+=sum[num[i]][q-1]-sum[num[i]][p];
        t[num[i]]++;
        if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i];
    }
    for(int i=sta[q];i<=r;i++){
        if(!t[num[i]]) t[num[i]]+=sum[num[i]][q-1]-sum[num[i]][p];
        t[num[i]]++;
        if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i];
    }//最后记得所有的清空 
    t[ans[p+1][q-1]]=0;
    for(int i=l;i<=end[p];i++) t[num[i]]=0;
    for(int i=sta[q];i<=r;i++) t[num[i]]=0;
    
    return b[now];
}
int main()
{
    int n,m,block,x,y,last=0;
    scanf("%d%d",&n,&m);
    block=(int)sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]),b[i]=a[i];//离散化 a是原数组 b是用来排序二分的数组 
        //为什么是这样求呢:若不能整除 相当于除完+1 能整除 又可以防止多+1 
        pos[i]=(i+block-1)/block;//pos是i所在块的位置 注意求法!! 
        if(!sta[pos[i]]) sta[pos[i]]=i;//sta end是一个块的起始和终止位置 
        end[pos[i]]=i;
    } 
    sort(b+1,b+1+n); 
    int l=unique(b+1,b+1+n)-b-1;//l存的是离散化后数的个数 其实是数的种类数 
    //注意一定是b+1+l!! 因为去重后b的元素是l 
    for(int i=1;i<=n;i++) num[i]=lower_bound(b+1,b+1+l,a[i])-b;//num存离散化后每一个数的排位 
    //预处理块与块间的众数是哪个 
    
    for(int i=1;i<=pos[n];i++){ 
        int now=0;//now临时记录众数是谁   t是桶 记录每一个数出现次数 
        for(int j=sta[i];j<=n;j++){//枚举的是从这个块起始位置向后的所有位置 
            t[num[j]]++;
            if(t[now]<t[num[j]]||(t[now]==t[num[j]]&&now>num[j])) now=num[j];//数较小也要更新 
            ans[i][pos[j]]=now;
        }
        for(int j=sta[i];j<=n;j++) t[num[j]]=0;//每次用完要记得还原!! 
    }
    //预处理每一个数在每一个块中出现的次数 
    for(int i=1;i<=n;i++) sum[num[i]][pos[i]]++;
    for(int i=1;i<=l;i++)//边界是l!!是因为枚举的是不同种类的数 
     for(int j=1;j<=pos[n];j++)//枚举每一块 
      sum[i][j]+=sum[i][j-1];//数的出现次数满足加减性 这一块由它本身和它前一块的出现次数累加得来 
    while(m--)
    {
        scanf("%d%d",&x,&y);
        x=(x+last-1)%n+1,y=(y+last-1)%n+1;
        if(x>y) swap(x,y);
        last=solve(x,y);
        printf("%d
",last);
    }
}
/*
6 3 
1 2 3 2 1 2 
1 5 
3 6 
1 5
*/
原文地址:https://www.cnblogs.com/mowanying/p/11234237.html