Luogu P4168 [Violet]蒲公英
Luogu P4168(分块)
看到数据范围 \(4e4\) 就基本可以猜到是分块做的题,而这道题要求能够查询 \([l,r]\) 的众数,这一点不满足区间可加性,因此,不能使用线段树和树状数组来进行维护,所以可以想到分块。
因为 \(a_i\in [1,10^9]\) ,所以用桶统计 \(a_i\) 的值时需要离散化。
将区间拆分成为 \(\sqrt{n}\) 个区块,因此每个区块中的元素个数为 \(\sqrt{n}\) 。
首先我们需要维护 \(s\) 和 \(f\) 两个数组, \(s_{i,j}\) 表示离散值 \(j\) 在前 \(i\) 个区块出现的总次数,直接暴力维护,时间复杂度为 \(O(n\sqrt{n})\) 。 \(f_{i,j}\) 表示区块 \(i\) 至区块 \(j\) 之间(包括 \(i\) 和 \(j\) )的众数的离散值,枚举 \(i,j\) ,再在区块 \(j\) 中枚举元素位置 \(k\) 进行处理,时间复杂度也为 \(O(n\sqrt{n})\) 。
对于每一次查询,如果 \(l\) 和 \(r\) 处于相同或相邻区块,那么直接在 \([l,r]\) 之间暴力统计即可,时间复杂度为 \(O(\sqrt{n})\) (因为区间长度为 \(\sqrt{n}\) );如果 \(l\) 和 \(r\) 不位于相同或相邻区块,那么将查询区间分为三部分:左侧非整区块,中间整区块,右侧非整区块,对于左右非整区块直接暴力统计每个值出现次数,而对于中间的整区块,直接使用开始时提前处理好的 \(s\) 和 \(f\) 即可,时间复杂度也为 \(O(n\sqrt{n})\) 。
注意在数据离散之后使用离散后的数据,分清楚此时的数据是否已经离散化,避免因非数据结构本身问题导致的过长调试时间。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
int read()
{
int k=0,flag=1;char b=getchar();
while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
return k*flag;
}
const int _SIZE=4e4;
int n,m;
int len,sum,cnt;
int a[_SIZE+5],b[_SIZE+5];
int val[_SIZE+5],pos[_SIZE+5];
int s[205][_SIZE+5],f[205][205],t[_SIZE+5];
struct BLOCK{
int l,r;
}block[205];
void PreWork()
{
len=sqrt(n);
sort(b+1,b+n+1);
sum=unique(b+1,b+n+1)-b-1;
cnt=(n-1)/len+1;
for (int i=1;i<=n;i++)
val[i]=lower_bound(b+1,b+sum+1,a[i])-b;
for (int i=1;i<=cnt;i++)
{
block[i].l=(i-1)*len+1;
block[i].r=min(n,i*len);
}
for (int i=1;i<=cnt;i++)
{
for (int j=block[i].l;j<=block[i].r;j++)
s[i][val[j]]++,pos[j]=i;
for (int j=1;j<=sum;j++)
s[i][j]+=s[i-1][j];
}
for (int i=1;i<=cnt;i++)
for (int j=i;j<=cnt;j++)
{
int most=f[i][j-1];
for (int k=block[j].l;k<=block[j].r;k++)
{
int appear=s[j][val[k]]-s[i-1][val[k]],mostAppear=s[j][most]-s[i-1][most];
//printf("%d~%d %d:%d\n",block[i].l,block[j].r,b[val[k]],appear);
if (appear>mostAppear || (appear==mostAppear && val[k]<most))
most=val[k];
}
f[i][j]=most;
}
}
int query(int l,int r)
{
int most=0;
if (l>r) swap(l,r);
if (pos[r]-pos[l]<=1)
{
for (int i=l;i<=r;i++)
t[val[i]]++;
for (int i=l;i<=r;i++)
if (t[val[i]]>t[most] || (t[val[i]]==t[most] && val[i]<most))
most=val[i];
}
else
{
for (int i=l;i<=block[pos[l]].r;i++)
t[val[i]]++;
for (int i=block[pos[r]].l;i<=r;i++)
t[val[i]]++;
most=f[pos[l]+1][pos[r]-1];
for (int i=l;i<=block[pos[l]].r;i++)
{
int mostAppear=s[pos[r]-1][most]-s[pos[l]][most]+t[most];
int currAppear=s[pos[r]-1][val[i]]-s[pos[l]][val[i]]+t[val[i]];
if (currAppear>mostAppear || (currAppear==mostAppear && val[i]<most))
most=val[i];
}
for (int i=block[pos[r]].l;i<=r;i++)
{
int mostAppear=s[pos[r]-1][most]-s[pos[l]][most]+t[most];
int currAppear=s[pos[r]-1][val[i]]-s[pos[l]][val[i]]+t[val[i]];
if (currAppear>mostAppear || (currAppear==mostAppear && val[i]<most))
most=val[i];
}
}
mem(t,0);
return b[most];
}
int main()
{
//freopen("P4168.in","r",stdin);
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=b[i]=read();
PreWork();
//for (int i=1;i<=cnt;i++) {printf("1~%d ",block[i].r);for (int j=1;j<=sum;j++){printf("%d:%d | ",b[j],s[i][j]);}puts("");}
//for (int i=1;i<=cnt;i++) printf("%d~%d %d\n",block[i].l,block[i].r,b[f[i][i]]);
int x=0;
while (m--)
{
int l=read(),r=read();
l=(l+x-1)%n+1,r=(r+x-1)%n+1;
x=query(l,r);
printf("%d\n",x);
}
return 0;
}