题意:
给定一个长度为
n
n
n的序列,求最长的满足区间众数有至少两种的区间长度。如果不存在这样的区间,输出
0
0
0。
两个版本,
e
a
s
y
easy
easy中
1
<
=
a
i
<
=
100
1<=a_i<=100
1<=ai<=100,
h
a
r
d
hard
hard中
1
<
=
a
i
<
=
n
1<=a_i<=n
1<=ai<=n
思路:
一个很重要的性质:
记整段序列的众数为
x
x
x,答案区间里
x
x
x一定也是众数之一;
反证法:若当前答案区间里
x
x
x出现的次数不是最多的,可以将区间向两边扩展,每次
x
x
x的数量都有可能增加
1
1
1,所有会有某个时间段,
x
x
x的出现次数跟最大的出现次数一样大,并且这段区间仍旧合法,并且比原先的区间更优,假设不成立。
- 由于 e a s y easy easy版本中, 1 < = a i < = 100 1<=a_i<=100 1<=ai<=100,在确定了众数 x x x后,我们可以枚举 y y y表示让 x x x跟 y y y出现次数一样多的话能够扩展到的最长区间长度。每次枚举的时候,维护一个新的数组:将等于 x x x的位置变为 + 1 +1 +1,将等于 y y y的位置变为 − 1 -1 −1,其他数则为0;问题就转化成了:给定一个序列,求最长的区间使得区间和为 0 0 0;
- 维护一个前缀和,并用 m a p map map记录一下前缀和为 s u m sum sum的最小下标,再遇到 s u m sum sum的话就取最大值维护答案;
- 时间复杂度为 O ( 100 n ) O(100n) O(100n)
- h a r d hard hard版本中, 1 < = a i < = n 1<=a_i<=n 1<=ai<=n,继续采用上述算法复杂度为 O ( n 2 ) O(n^2) O(n2),考虑用根号分治思想平衡预处理跟询问。
- 记录每个数出现的次数 c n t [ i ] cnt[i] cnt[i].对于 c n t [ i ] > = n cnt[i]>=\sqrt n cnt[i]>=n的数,继续采取上述算法,这样的数的个数不超过 n \sqrt n n,所以这部分的复杂度为 O ( n n ) O(n\sqrt n) O(nn)
- 对于 c n t [ i ] < = n cnt[i]<=\sqrt n cnt[i]<=n的数,枚举出现次数 z z z,假设出现次数为 z z z的数可以作为众数,用双指针维护能够扩展到的最大区间。
- 如果这个序列有两个众数的话,答案就是 n n n
代码:
D 1 D1 D1
const int maxn=2e5+100;
int n,a[maxn];
unordered_map<int,int>mp;//前缀和标记数组
int main(){
n=read;
int res=0,cnt=0,pos;
rep(i,1,n){
a[i]=read;
mp[a[i]]++;
if(res<mp[a[i]]){//记录众数 res表示众数出现的次数,pos表示众数,cnt表示众数的个数
res=mp[a[i]];pos=a[i];cnt=1;
}
else if(res==mp[a[i]]) cnt++;
}
if(cnt>1){
write(n);
return 0;
}
int ans=0;
for(int i=1;i<=100;i++){//枚举第二个数
if(i==pos) continue;//两者不能相等
mp.clear();mp[0]=0;//每次清空数组,注意有可能本身就是$0$
int sum=0;
for(int j=1;j<=n;j++){
if(a[j]==i) sum--;
else if(a[j]==pos) sum++;
if(mp.count(sum)) ans=max(ans,j-mp[sum]);//以前出现过的话更新答案
else mp[sum]=j;//记录下标最小的位置
}
}
write(ans);
return 0;
}
D 2 D2 D2
const int maxn=2e5+100;
int n,a[maxn],cnt[maxn],ans=0,maxx;
bool st[maxn];
//unordered_map<int,int>mp;
int mp[maxn*2];//防止下标为负,整体偏移
void solve1(int x){
rep(i,1,n) mp[i]=mp[i+n]=-1;
int sum=n;mp[n]=0,mp[0]=0;
for(int i=1;i<=n;i++){
if(a[i]==maxx) sum++;
else if(a[i]==x) sum--;
if(mp[sum]!=-1) ans=max(ans,i-mp[sum]);
else mp[sum]=i;
}
}
void solve2(int x){
rep(i,1,n) mp[i]=0;
int l=1,tot=0;//记录左端点,tot表示出现次数为x的数的个数
mp[a[l]]++;
if(mp[a[l]]==x) tot++;
rep(i,2,n){
mp[a[i]]++;
if(mp[a[i]]==x) tot++;//更新tot
else if(mp[a[i]]>x){
while(mp[a[i]]>x){//移动左端点直到区间合法
mp[a[l]]--;
if(mp[a[l]]==x-1) tot--;//更新tot
l++;
}
}
if(tot>=2) ans=max(ans,i-l+1);//合法区间的判断
}
}
int main(){
n=read;
rep(i,1,n){
a[i]=read;
cnt[a[i]]++;
if(cnt[maxx]<cnt[a[i]]) maxx=a[i];
}
int m=sqrt(n);
rep(i,1,n){
if(cnt[i]>=m&&!st[i]&&i!=maxx){//出现次数大于sqrtn的采取D1的写法
st[i]=1;
solve1(i);
}
}
for(int i=1;i<m;i++) solve2(i);//枚举次数
write(ans);
return 0;
}