【洛谷P5048】Yuno loves sqrt technology III
题目
题目链接:https://www.luogu.com.cn/problem/P5048
给你一个长为 \(n\) 的序列 \(a\),\(m\) 次询问,每次查询一个区间的众数的出现次数,强制在线。
\(n,m\leq 5\times 10^5;a_i\leq 10^9\)。
思路
第一反应是直接在蒲公英做法的基础上对于每一个数字维护一个 vector,找出区间众数后二分一下即可。
但是空间限制只有 \(62.5\)MB,原来那个空间复杂度 \(O(n\sqrt{n})\) 的做法是肯定过不了的。
分块,依然是预处理 \(\text{maxc}[l][r]\) 表示第 \(l\) 个块到第 \(r\) 个块众数的出现次数,对每一个数字维护一个 vector 存下这个数字的出现位置。
对于一个询问,先令 \(ans\) 为中间整块的众数出现次数,然后枚举散块中的所有数字,拿左边的散块为例,假设散块中一个数字在其 vector 的位置为 \(p\),如果 vector 中第 \(p+ans\) 个数字在数组中的下标 \(\leq\) 询问的右端点,那么就让 \(ans+1\)。
显然 \(ans\) 加的次数上界是散块长度,所以时间复杂度是 \(O(m\sqrt{n})\) 的,空间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=500010,M=710;
int n,m,B,ans,a[N],L[M],R[M],bel[N],rk[N],cnt[N],maxc[M][M];
vector<int> pos[N];
map<int,int> id;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if (!id[a[i]]) id[a[i]]=++B;
a[i]=id[a[i]];
pos[a[i]].push_back(i);
rk[i]=pos[a[i]].size()-1;
}
B=sqrt(n)+1;
for (int i=1;i<=B;i++)
{
memset(cnt,0,sizeof(cnt));
L[i]=R[i-1]+1; R[i]=min(i*B,n);
for (int j=L[i];j<=R[i];j++) bel[j]=i;
for (int j=i;j>=1;j--)
{
maxc[j][i]=maxc[j+1][i];
for (int k=L[j];k<=R[j];k++)
maxc[j][i]=max(maxc[j][i],++cnt[a[k]]);
}
}
while (m--)
{
int l,r;
scanf("%d%d",&l,&r);
l^=ans; r^=ans; ans=maxc[bel[l]+1][bel[r]-1];
for (int i=l;i<=min(R[bel[l]],r);i++)
while (rk[i]+ans<pos[a[i]].size() && pos[a[i]][rk[i]+ans]<=r) ans++;
for (int i=r;i>=max(L[bel[r]],l);i--)
while (rk[i]-ans>=0 && pos[a[i]][rk[i]-ans]>=l) ans++;
cout<<ans<<"\n";
}
return 0;
}