区间众数、蒲公英(分块思想)

区间众数(分块思想)

题目链接:蒲公英
拓展题:作诗

题目大意:给一个数组,q次在线查询,每次查询L-R中出现次数最多且最小的数。

解题思路:
	首先我们把这个数组分为k块来看待,然而每次查询的L-R则有几种可能,如下图

在这里插入图片描述

	1、对于第一种情况,当L与R属于同一个区间时,我们只需要暴力计算即可

	2、对于第二种情况,我们需要把L到L所在的块的右边界的数统计,再把R所在的块的左边界到R的数统计即可
	
	3、对于三种情况,我们预处理出所以数在任意两块中出现的次数,
	然后再暴力计算L到L所在的块的右边界的数再把R所在的块的左边界到R的数,相加即可。

过程中会使用到前缀和思想,即求一个数在任意两个区间出现的次数表示为sum[y][j]-sum[x-1][j],意思是第x块到第y块区间j出现的次数

sum[i][j]:表示为从开始到第i块位置,j出现的次数
cnti[i]:表示为i出现的次数
MIN[i][j]:表示为第i块到第j块出现次数最多且最小的数

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=4e4+7,M=207;
int a[N],A[N],Len,n,q;//a表示存放数组,A表示a的离散化,Len表示离散化后数组长度
int B[N],BL;//B[i]表示i下标属于哪一块,BL表示块的长度
int sum[M][N],MIN[M][M],cnt[N];
//sum[i][j]表示从开始到第i块j出现的次数,MIN[i][j]表示第i块到第j块出现次数最多且最小的数,cnt[i]表示i出现的次数
void Build(){
    for(int i=1;i<=B[n];i++){//枚举每一块,B[n]表示n所在的块,也就是块的总数
        for(int j=1;j<=Len;j++)sum[i][j]=sum[i-1][j];//当前i块的j出现的次数,肯定包括i-1块j出现的次数
        for(int j=(i-1)*BL+1;j<=n&&j<=i*BL;j++)sum[i][a[j]]++;//再从i块的最左侧开始枚举j
    }

    for(int i=1;i<=B[n];i++){//枚举每一块
        int ans=0,res=0;//ans记录当前出现次数最多且最小的数,res记录当前出现最多的次数
        for(int j=(i-1)*BL+1;j<=n;j++){//从当前i块到最后为止
            cnt[a[j]]++;//a[j]出现的次数
            if(cnt[a[j]]>res||cnt[a[j]]==res&&A[a[j]]<A[ans]){//如果出现的次数大于之前或者次数相等于之前,当数小于之前就更新
                res=cnt[a[j]];
                ans=a[j];
            }
            MIN[i][B[j]]=ans;//当前i块到j所在的块的当前答案
        }
        memset(cnt,0,sizeof cnt);
    }
}

int query(int L,int R){
    int x=B[L],y=B[R],ans=MIN[x+1][y-1],res=max(0,sum[y-1][ans]-sum[x][ans]),lim=min(x*BL,R),f=0;
    //x表示L所在的块,y表示R所在的块,ans表示L-R中间的块的出现次数最多且最小的数(不包括L、r所在的块),如果L与R中间没有块,则为0。
    //res表示L-R中间的块的出现最多的数的次数(不包括L、r所在的块),这里利用前缀和思想,如果L与R中间没有块,则为0。
    //lim表示L所在的块的边界(如果R与L在同一块则到R中止),f表示L、R是否在同一块。

    if(x!=y)f=1;
    for(int i=L;i<=lim;i++){
        cnt[a[i]]=max(0,sum[y-1][a[i]]-sum[x][a[i]]);//前缀和求L所在的块出现的数,在L-R中出现的次数(不包括L、r所在的块)
    }
    if(f){
        for(int i=(y-1)*BL+1;i<=R;i++){
            cnt[a[i]]=max(0,sum[y-1][a[i]]-sum[x][a[i]]);//前缀和求R所在的块出现的数,在L-R中出现的次数(不包括L、r所在的块)
        }
    }
    for(int i=L;i<=lim;i++){//暴力枚举L所在的块
        cnt[a[i]]++;
        if(cnt[a[i]]>res||cnt[a[i]]==res&&A[a[i]]<A[ans]){//如果满足条件就更新答案
            res=cnt[a[i]];
            ans=a[i];
        }
    }
    if(f){
        for(int i=(y-1)*BL+1;i<=R;i++){//暴力枚举R所在的块
            cnt[a[i]]++;
            if(cnt[a[i]]>res||cnt[a[i]]==res&&A[a[i]]<A[ans]){//如果满足条件就更新答案
                res=cnt[a[i]];
                ans=a[i];
            }
        }
    }
    return ans;//返回的答案是离散化后的结果
}
int main(){
    cin>>n>>q;BL=sqrt(n);//块的长度
    for(int i=1;i<=n;i++){
        cin>>a[i];
        A[i]=a[i];
        B[i]=(i-1)/BL+1;//当前i下标属于哪一块
    }
    //离散化处理
    sort(A+1,A+1+n);
    Len=unique(A+1,A+1+n)-A-1;
    for(int i=1;i<=n;i++)a[i]=lower_bound(A+1,A+1+Len,a[i])-A;
    
    Build();//建块预处理
    
    int x=0,L,R;//题目输入要求,在线处理
    while(q--){
        cin>>L>>R;
        L=(L+x-1)%n+1,R=(R+x-1)%n+1;
        if(L>R)swap(L,R);
        x=A[query(L,R)];//query返回的是离散化后的下标
        cout<<x<<endl;
    }
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值