【洛谷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;
}
posted @ 2021-05-30 22:07  stoorz  阅读(51)  评论(0编辑  收藏  举报