《算法竞赛进阶指南》0x44 分块 在线求区间众数

题目链接:https://www.acwing.com/problem/content/description/251/

题目中下一次的询问一定要在上一次的答案的基础上计算,所以属于在线回答的题目,此时只能用分块进行求解,有两种分块的策略。

1.维护一个数组,记录在块断开的两个位置中的每个数出现的次数,然后求[l,r]的众数数量的时候只需要在完整分块的基础上进行叠加顺便更新答案即可,这里计算的时候可以设置一个x,y来代表完整区间的开始位置和结束位置,如果没有一个中间的完整区间,x,y就是0,属于一点技巧性的东西。

2.区间内的众数要么是完整区间中的众数,要么是不完整片段中没一个出现的数,所以只需要对这些数在其出现的位置构成的vector中二分搜索在[l,r]区间之内的数量即可,然后更新答案。

方法一的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int T = 37;
const int maxn = 40010;
int a[maxn],b[maxn],L[T],R[T],c[T][T][maxn],f[T][T][2],now[2];
int pos[maxn];
//0:众数的数量,1:众数
inline void  work(int x,int y ,int num){//加数并且统计众数 
    ++c[x][y][num];
    //取数量大的,数量一样取较小的 
    if(c[x][y][num]>now[0] || (c[x][y][num]==now[0] && now[1]>num)){
        now[0]=c[x][y][num];
        now[1]=num;
    }
    return ;
}
int query(int l,int r){
    int p=pos[l],q=pos[r];
    int x=0,y=0;
    if(p+1<=q-1){
        x=p+1;
        y=q-1;
    }
    memcpy(now,f[x][y],sizeof(now));//将众数信息初始化 
    if(p==q){//此时是在0,0上进行操作,所有数的统计次数是0 
        for(int i=l;i<=r;i++)work(x,y,a[i]);
        for(int i=l;i<=r;i++)--c[x][y][a[i]];
    }else{
        for(int i=l;i<=R[p];i++)work(x,y,a[i]);
        for(int i=L[q];i<=r;i++)work(x,y,a[i]);
        for(int i=l;i<=R[p];i++)--c[x][y][a[i]];//还原状态 
        for(int i=L[q];i<=r;i++)--c[x][y][a[i]];
    }
    return b[now[1]];//now[1]中保存的是索引 
}
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    memcpy(b,a,sizeof(a));
    sort(b+1,b+n+1);//在b中离散化,将索引保存在a中 
    int tot = unique(b+1,b+n+1)-(b+1);
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
    }
    int t=pow((double)n,(double)1/3);
    int len=t?n/t:n;//n^(1/3)为0的话长度就是n,否则可以划分分块 
    for(int i=1;i<=t;i++){
        L[i]=(i-1)*len+1;
        R[i]=i*len;
    }
    if(R[t]<n){
        ++t;
        L[t]=R[t-1]+1;
        R[t]=n;
    }
    for(int i=1;i<=t;i++)
        for(int j=L[i];j<=R[i];j++){
            pos[j]=i;
        }
        
    memset(f,0,sizeof(f));
    memset(c,0,sizeof(c));
    for(int i=1;i<=t;i++)//处理出每个[L,R]处的众数 
        for(int j=i;j<=t;j++){
            for(int k=L[i];k<=R[j];k++)
                c[i][j][a[k]]++;
            for(int k=1;k<=tot;k++){
                if(c[i][j][k]>f[i][j][0]){//保存众数 
                    f[i][j][0]=c[i][j][k];
                    f[i][j][1]=k;
                }
            } 
        }
    
    int x=0;
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        l=(l+x-1)%n+1;
        r=(r+x-1)%n+1;
        if(l>r)swap(l,r);
        x=query(l,r);
        printf("%d
",x);
    }
    return 0;
} 

方法二的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 40010;
const int T = 900;//最多被分成大约785个分块 
int c[maxn],a[maxn],b[maxn],f[T][T];
int pos[maxn],L[T],R[T];
vector<int> v[maxn];
int get(int x,int l,int r){
    return upper_bound(v[x].begin(),v[x].end(),r)
            -lower_bound(v[x].begin(),v[x].end(),l);
}
void find(int x,int l,int r,int& ans,int& cnt){
    int num=get(x,l,r);
    if(num>cnt || (num==cnt && x<ans)){
        ans=x;
        cnt=num;
    } 
}
int query(int l,int r){
    int p=pos[l],q=pos[r];
    int ans=0,cnt=0;
    if(p==q){
        for(int i=l;i<=r;i++) find(a[i],l,r,ans,cnt);
        return b[ans];
    }
    int x=0,y=0;
    if(p+1<=q-1){
        x=p+1;
        y=q-1;
    }
    for(int i=l;i<=R[p];i++)find(a[i],l,r,ans,cnt);
    for(int i=L[q];i<=r;i++)find(a[i],l,r,ans,cnt);
    if(f[x][y])//中间存在完整片段 
        find(f[x][y],l,r,ans,cnt);
    return b[ans];
}
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    memcpy(b,a,sizeof(b));
    sort(b+1,b+n+1);
    int tot=unique(b+1,b+n+1)-(b+1);
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
        v[a[i]].push_back(i);
    } 
    int t=sqrt(log(n)/log(2)*n);
    int len=t?n/t:n;
    for(int i=1;i<=t;i++){
        L[i]=(i-1)*len+1;
        R[i]=i*len;
    }
    if(R[t]<n){
        ++t;
        L[t]=R[t-1]+1;
        R[t]=n;
    }
    for(int i=1;i<=t;i++)
        for(int j=L[i];j<=R[i];j++)
            pos[j]=i;
    memset(f,0,sizeof(f));
    for(int i=1;i<=t;i++){//对每个分块都扫描整个长度,得出以该左端点为起点的序列的众数 
        memset(c,0,sizeof(c));
        int ans=0,cnt=0;
        for(int j=L[i];j<=n;j++){
            ++c[a[j]];
            if(c[a[j]]>cnt || (c[a[j]]==cnt && a[j]<ans)){
                ans=a[j];
                cnt=c[a[j]];
                
            }    
            f[i][pos[j]]=ans;
        } 
    }
    
    int x=0;
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        l=(l+x-1)%n+1;
        r=(r+x-1)%n+1;
        if(l>r)swap(l,r);
        x=query(l,r);
        printf("%d
",x);
    }
    return 0;
}
每一个不曾起舞的日子,都是对生命的辜负。
原文地址:https://www.cnblogs.com/randy-lo/p/13355720.html