#根号分治,前缀和,双指针#CF1446D2 Frequency Problem (Hard Version)
题目
给定一个长度为 \(n\) 的序列,问是否存在一个最长的区间使得至少存在两个众数。
分析
实际上 Easy Version 是用来启发大于根号的做法的。
众数可以说有一个性质吧,答案区间中的其中一个众数一定是整个序列的众数。
当然如果整个序列有多个众数答案就是 \(n\),如果只有一种数答案就是 \(0\)。
只需要考虑一种众数的情况,先看 Easy Version(数字种类不超过一百)。
如果数字种类数足够少,直接枚举数字种类,那么相当于一段区间众数和该种数字出现次数相等。
那么将众数视为 \(-1\),该种数字视为 \(1\),即求是否存在一段区间和为零,那直接前缀和记录最早位置即可。
但是 Hard Version 的时候不能所有数字都算一遍,
考虑枚举出现次数 \(T\),然后用一个双指针求出对于每一个右端点 \(r\),求出最小的 \(l\) 使得 \([l,r]\) 数字出现次数不超过 \(T\)。
这样只要有至少两个数字出现次数为 \(T\),\([l,r]\) 就是一段符合要求的区间。
将两种方法结合一下,\(T\) 只枚举到根号,枚举数字种类只枚举出现次数超过根号的数字,这样就是 \(O(n\sqrt{n})\) 的
代码
#include <cstdio>
#include <cctype>
using namespace std;
const int N=200011,bl=400;
int n,mx,se,cnt[N],a[N],c[N],las[N<<1],ans;
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int max(int a,int b){return a>b?a:b;}
int main(){
n=iut();
for (int i=1;i<=n;++i) ++cnt[a[i]=iut()];
for (int i=1;i<=n;++i)
if (cnt[i]>cnt[mx]) se=mx,mx=i;
else if (cnt[i]>cnt[se]) se=i;
if (!se) return !printf("0");
if (cnt[mx]==cnt[se]) return !printf("%d",n);
for (int T=1;T<=bl;++T){
int now=0;
for (int i=1,j=1;i<=n;++i){
for (;c[a[i]]==T;--c[a[j++]])
if (c[a[j]]==T) --now;
if (++c[a[i]]==T) ++now;
if (now>1) ans=max(ans,i-j+1);
}
for (int i=0;i<=n;++i) c[i]=0;
}
for (int i=1;i<=n;++i)
if (cnt[i]>bl&&i!=mx){
for (int j=0;j<=2*n;++j) las[j]=-1; las[n]=0;
for (int j=1,s=0;j<=n;++j){
if (a[j]==i) ++s;
else if (a[j]==mx) --s;
if (las[s+n]>=0) ans=max(ans,j-las[s+n]);
else las[s+n]=j;
}
}
return !printf("%d",ans);
}