题解:P8330 [ZJOI2022] 众数
Danslapiscine · · 题解
根号分治大成题。蒟蒻不看题解想了一天。
题目意思就是在序列中删掉一区间,分别取剩下的段和删掉的段中的众数的个数,问他们的和最大能为多少,还有能取到最大和的剩下那段众数的方案。
直接做是不行的,用根号分治的方法创造条件。
1. 处理小于 \sqrt{n} 个数字。
这种情况容易想到对每个数字分开处理,花费
设处理到的数字是
对于该数在区间外面的情况,可设
这样我们可以处理较大的
2. 每个数字出现次数小于等于 \sqrt{n} 。
发现区间内和区间外的众数个数均小于等于
因为众数个数随区间长度增加单调递增,所以可以从后往前求解,设当前点为
时间复杂度
#include <bits/stdc++.h>
using namespace std;
int TAT;
int n, parter, a[500002], nn, aa[500002], cnt[500002];
int Ans, haveAns[500002];
void update(int s,int num){
if(s > Ans) Ans = s, haveAns[num] = Ans;
else if(s == Ans) haveAns[num] = Ans;
}
int adder, have[500002], Max[500002];
int f[500002];
void solve1(){
for(int i=1;i<=nn;i++){
if(cnt[i] <= parter) continue;
// 把i看成1,把a[j]看成-1,前缀和have[a[j]]
// 全局加1 单点-1 查询单点历史最小值
for(int j=1;j<=nn;j++) have[j] = 0, Max[j] = 0;
adder = 0;
for(int j=1;j<=n-1;j++){
if(a[j] == i) adder++;
have[a[j]]--, Max[a[j]] = max(Max[a[j]], -have[a[j]]-adder);
update(cnt[a[j+1]]+have[a[j+1]]+adder+Max[a[j+1]], a[j+1]);
}// 在内
adder = 0;
for(int j=1;j<=nn;j++) f[j] = 0;
for(int j=1;j<=n;j++){
if(a[j] == i) adder++;
f[a[j]] = max(f[a[j]] + 1, adder + 1);
update(f[a[j]] + cnt[i] - adder, i);
}// 在外
}
}
int head[500002], nxt[500002], zhong[500002];
void solve2(){
for(int i=1;i<=nn;i++) head[i] = n+1;
nxt[n+1] = 0;
for(int i=1;i<=n;i++) zhong[i] = n+1;
for(int i=n;i>=1;i--){
if(cnt[a[i]] <= parter) {
nxt[i] = head[a[i]], head[a[i]] = i;
}
}
for(int i=n;i>=1;i--){
if(cnt[a[i]] > parter) continue;
int cnt2 = 0;
for(int pos1=nxt[i],cnt1=0;pos1;pos1=nxt[pos1],cnt1--){
while(cnt2 + 1 <= parter && zhong[cnt2+1] < pos1) cnt2++;
update(cnt[a[i]]+cnt1+cnt2,a[i]);
}
for(int j=i,k=1;j!=n+1;j=nxt[j],k++){
zhong[k] = min(zhong[k], j);
}
}
}
int main(){
//freopen("mode_ex2.in","r",stdin);
//freopen("zzz.out","w",stdout);
scanf("%d",&TAT);
while(TAT--){
Ans = 0;
for(int i=1;i<=n;i++) haveAns[i] = 0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]), aa[i] = a[i];
sort(aa+1,aa+n+1), nn = unique(aa+1,aa+n+1)-aa-1;
for(int i=1;i<=nn;i++) cnt[i] = 0;
for(int i=1;i<=n;i++) a[i] = lower_bound(aa+1,aa+nn+1,a[i])-aa, cnt[a[i]]++;
parter = sqrt(n);
solve1();
reverse(a+1,a+n+1);
solve1();
solve2();
reverse(a+1,a+n+1);
solve2();
printf("%d\n",Ans);
for(int i=1;i<=nn;i++) {
if(haveAns[i] == Ans) printf("%d\n",aa[i]);
}
}
return 0;
}
/*
1
6
2 4 2 4 8 8
8 8 4 2 4 2
*/