【洛谷P5048】Yuno loves sqrt technology III 题目 思路 代码

题目链接:https://www.luogu.com.cn/problem/P5048
给你一个长为 (n) 的序列 (a)(m) 次询问,每次查询一个区间的众数的出现次数,强制在线。
(n,mleq 5 imes 10^5;a_ileq 10^9)

思路

第一反应是直接在蒲公英做法的基础上对于每一个数字维护一个 vector,找出区间众数后二分一下即可。
但是空间限制只有 (62.5)MB,原来那个空间复杂度 (O(nsqrt{n})) 的做法是肯定过不了的。

分块,依然是预处理 ( ext{maxc}[l][r]) 表示第 (l) 个块到第 (r) 个块众数的出现次数,对每一个数字维护一个 vector 存下这个数字的出现位置。
对于一个询问,先令 (ans) 为中间整块的众数出现次数,然后枚举散块中的所有数字,拿左边的散块为例,假设散块中一个数字在其 vector 的位置为 (p),如果 vector 中第 (p+ans) 个数字在数组中的下标 (leq) 询问的右端点,那么就让 (ans+1)
显然 (ans) 加的次数上界是散块长度,所以时间复杂度是 (O(msqrt{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<<"
";
	}
	return 0;
}