区间众数(分块思想)
题目链接:蒲公英
拓展题:作诗
题目大意:给一个数组,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;
}