[Luogu P4168] [Violet]蒲公英 (分块)
题面
Solution
题目要求求出区间众数,强制在线。
区间众数是一个比较尴尬的问题,我们无法用区间数据结构来处理这个问题,因为我们没法很好的合并区间众数的答案。
既然区间数据结构解决不了这个问题,我们可以考虑一下使用基于分块的算法,例如莫队。
这题用莫队非常好处理,不幸的是,这题要求强制在线。
因此我们考虑使用分块算法。
分块算法的核心在于把一整个块的信息压缩起来以便快速处理。
我们要查询一段区间的众数,我们可以考虑这样搞:对于这个区间内连续的块,我们先快速地查询这个连续的块中的众数,然后我们暴力处理这个区间剩余的左右两个零散的点,开一个桶暴力维护这些零散的点每个颜色出现的次数,每新加入一个点,就与整个区间的答案比较一下,如果更优就替换答案。
为了实现上面那个思路,我们必须要实现两点:快速求出一段连续块的每个颜色出现的次数,快速求出一段连续块的众数。
对于第一个问题,解决方法很简单,我们暴力做前缀和即可,复杂度\(O(n*\sqrt n)\)
for(int i=1;i<=cnt_block;i++)
{
for(int j=1;j<=to;j++)
pre[i][j]=pre[i-1][j];
for(int j=(i-1)*size;j<i*size;j++)
pre[i][a[j]]++;
}
对于第二个问题,我们可以考虑把每段连续块的答案预处理出来。
具体做法是:我们枚举每个块,然后我们暴力往后扫描,每扫到一个块的结尾就记录答案。
cnt[0]=-0x3f3f3f3f;
for(int i=1;i<=cnt_block;i++)
{
int t_ans=0;
for(int j=(i-1)*size;j<=n;j++)
{
cnt[a[j]]++;
if(cnt[a[j]]>cnt[t_ans] or (cnt[a[j]]==cnt[t_ans] and a[j]<t_ans))
t_ans=a[j];
if((j+1)%size==0)
f[i][j/size+1]=t_ans;
}
memset(cnt,0,sizeof cnt);
cnt[0]=-0x3f3f3f3f;
}
就酱,这题就被我们搞定啦~
复杂度\(O(n\sqrt n)\)
Code
本题实现上有较多细节要处理,请小心
//Luogu P4168 [Violet]蒲公英
//Feb,4th,2019
//分块套路题
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
long long read()
{
long long x=0,f=1; char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int N=40000+1000;
const int M=200+10;
int n,m,a[N],b[N],t_n,mmap[N];//离散值->原值
int f[M][M],pre[M][N];//f[i][j]:块i到j的众数,pre[i][j]:到i块为止,颜色j的出现次数前缀和
int cnt[N];//零时记录每个元素出现次数
int main()
{
//freopen("testdata.in","r",stdin);
//freopen("4168.out","w",stdout);
t_n=n=read(),m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[i]=a[i];
sort(b+1,b+1+n);
int to=0,last=0;
for(int i=1;i<=n;i++)
if(b[i]!=last)
last=b[i],b[++to]=b[i];
for(int i=1;i<=n;i++)
{
int t=lower_bound(b+1,b+1+to,a[i])-b;
mmap[t]=a[i],a[i]=t;
}
int size=int(sqrt(n)),cnt_block=n/size+1;
n=cnt_block*size-1;
for(int i=1;i<=cnt_block;i++)
{
for(int j=1;j<=to;j++)
pre[i][j]=pre[i-1][j];
for(int j=(i-1)*size;j<i*size;j++)
pre[i][a[j]]++;
}
cnt[0]=-0x3f3f3f3f;
for(int i=1;i<=cnt_block;i++)
{
int t_ans=0;
for(int j=(i-1)*size;j<=n;j++)
{
cnt[a[j]]++;
if(cnt[a[j]]>cnt[t_ans] or (cnt[a[j]]==cnt[t_ans] and a[j]<t_ans))
t_ans=a[j];
if((j+1)%size==0)
f[i][j/size+1]=t_ans;
}
memset(cnt,0,sizeof cnt);
cnt[0]=-0x3f3f3f3f;
}
int ans=0;
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
l=(l+ans-1)%t_n+1,r=(r+ans-1)%t_n+1;
if(l>r) swap(l,r);
int bl=l/size+1,br=r/size+1;
ans=0;
if(bl+1<=br-1)
ans=f[bl+1][br-1];
for(int j=l;j<bl*size and j<=r;j++)
{
cnt[a[j]]++;
int tmp1=cnt[a[j]],tmp2=cnt[ans];
if(bl+1<=br-1)
tmp1+=pre[br-1][a[j]]-pre[bl][a[j]],
tmp2+=pre[br-1][ans]-pre[bl][ans];
if(tmp1>tmp2 or (tmp1==tmp2 and a[j]<ans))
ans=a[j];
}
if(bl!=br)
for(int j=(br-1)*size;j<=r;j++)
{
cnt[a[j]]++;
int tmp1=cnt[a[j]],tmp2=cnt[ans];
if(bl+1<=br-1)
tmp1+=pre[br-1][a[j]]-pre[bl][a[j]],
tmp2+=pre[br-1][ans]-pre[bl][ans];
if(tmp1>tmp2 or (tmp1==tmp2 and a[j]<ans))
ans=a[j];
}
for(int j=l;j<bl*size and j<=r;j++)
cnt[a[j]]--;
if(bl!=br)
for(int j=(br-1)*size;j<=r;j++)
cnt[a[j]]--;
cnt[0]=-0x3f3f3f3f;
ans=mmap[ans];
printf("%d\n",ans);
}
return 0;
}
自己选择的路,跪着也要走完。朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。