题目
题目链接
大意:求区间众数
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=4e4+10;
int n,m,t,len,sz,ans,L,R,cnt,num;
int a[N],b[N],F[N],st[40],ed[40],c[40][40][N],f[40][40],d[40][40];
//c[i][j][k]表示以段边界为端点的区间[i,j]中排名为k的数的出现次数
//d[i][j]表示i段到j段的众数排名
//f[i][j]表示i段到j段的众数出现次数
void Init()
{
t=(int)pow(n*1.0,1.0/3);//每段长度
if(t)len=n/t;
for(int i=1;i<=t;i++)st[i]=(i-1)*len+1,ed[i]=i*len;
if(ed[t]<n)st[t+1]=ed[t]+1,ed[++t]=n;
sort(b+1,b+n+1);
sz=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)F[i]=lower_bound(b+1,b+sz+1,a[i])-b;//离散化去重后储存排名
for(int i=1;i<=t;i++)for(int j=i;j<=t;j++)
{
for(int k=st[i];k<=ed[j];k++)c[i][j][F[k]]++;
for(int k=1;k<=sz;k++)
if(c[i][j][k]>f[i][j]||(c[i][j][k]==f[i][j]&&k<d[i][j]))f[i][j]=c[i][j][k],d[i][j]=k;
}
}
void update(int i)
{
c[L][R][F[i]]++;
if(c[L][R][F[i]]>cnt||(c[L][R][F[i]]==cnt&&F[i]<num))cnt=c[L][R][F[i]],num=F[i];
}
int solve(int x,int y)
{
int l,r;//起始段终止段
if(x>y)swap(x,y);
for(int i=1;i<=t;i++)if(x<=ed[i]){l=i;break;}
for(int i=t;i;i--)if(y>=st[i]){r=i;break;}
if(l+1<=r-1)L=l+1,R=r-1;else L=R=0;
cnt=f[L][R],num=d[L][R];
if(l==r)
{
for(int i=x;i<=y;i++)update(i);
for(int i=x;i<=y;i++)c[L][R][F[i]]--;
}
else
{
for(int i=x;i<=ed[l];i++)update(i);
for(int i=st[r];i<=y;i++)update(i);//朴素扫描不完整的段 更新答案
for(int i=x;i<=ed[l];i++)c[L][R][F[i]]--;
for(int i=st[r];i<=y;i++)c[L][R][F[i]]--;//数组复原
}
return b[num];
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];
Init();
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
ans=solve((x+ans-1)%n+1,(y+ans-1)%n+1);
printf("%d\n",ans);
}
return 0;
}
总结
因为众数不具有区间可加性,所以用树状数组或者线段树维护就十分困难。我们可以采用分块算法。
把序列a分成T块,每块长度L=N/T
对于每个询问[l,r],设l处于第p块,r处于第q块。把区间[l,r]分成三部分
1.开头不足一整段的[l,L)
2.第p+1~q-1块构成的区间[L,R]
3.结尾不足一整段的(R,r]
a序列在区间[l,r]的众数只可能来自:
1.区间[L,R]的众数
2.出现在[l,L)和(R,r]之间的数。