「ZJOI2022」众数
好难。
题意:给定序列 \(a\),选择一个连续子序列使得子序列内众数与剩下部分众数出现次数和最大,求出现次数和的最大值并给出剩下部分众数的可能情况。
看到众数先想根号分治。考虑阈值 \(B\),出现次数 $ > B$ 的称为大数,否则称为小数。大数只有 \(O(\frac{N}{B})\) 个,考虑直接枚举大数,那么有两种情况是有价值的:
- 大数为剩下部分众数,那么就是要选一个子序列使得这个子序列内众数出现次数加上大数出现次数最大。可以直接假定一开始没有子序列,那么选了一个子序列的贡献就是其中众数出现次数与其中大数出现次数之差,显然这是一个最大子段和问题。想到可以枚举子序列众数,只有子序列众数出现次数个点是有价值的,这样这部分总复杂度就是 \(O(\frac{n^2}{B})\)。
- 大数为中间众数。和第一种情况类似,先枚举剩下部分众数,然后假定没有选子序列,然后转化为最大子段和。
接下来考虑两部分的众数都是小数的情形。
做一个尝试,先枚举剩下部分众数,那么同样只有众数出现次数个点是有用的,也就意味着可能选出的子序列个数是平方级别。注意到出现次数 \(\le B\),因此可能选出的子序列总个数是 \(O(nB)\) 的。考虑一个暴力的想法:直接枚举出所有可能的子序列,然后只要能 \(O(1)\) 的计算出其中众数出现次数即可。这乍一看是不可实现的,但不要忘记众数出现个数 \(\le B\)。
不难发现,区间众数出现次数呈现为若干段的形式,而值域限制了其段数为 \(O(B)\)。考虑记录每个段的第一个点维护信息,如果可以在合适的复杂度内维护这一信息,我们就可以利用众数出现次数单调性做到均摊 \(O(1)\) 的维护众数出现次数了。
现在问题转化为维护 \(r_{i,j}\) 表示以 \(i\) 为左端点,\(j\) 为区间众数出现次数,最小的右端点。可以先枚举\(j\),然后利用two-pointers快速计算。总复杂度为 \(O(nB)\)。
综上,可以在 \(O(\frac{n^2}{B} + nB)\) 的复杂度内解决问题,取 \(B = \sqrt{n}\) 可得最优复杂度 \(O(n\sqrt n)\)。
点我看代码(〒︿〒)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define LL long long
using namespace std;
template <typename T>
inline void read(T &x) {
x = 0; int f = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
if(f) x = ~x + 1;
}
const int N = 2e5 + 10;
const int B = 450;
int n, a[N], b[N], app[N];
vector<int> pos[N];
int v[N];
int pre[N], buc[N];
int rpos[460][N];
inline void update(int &x, int y) {x = max(x, y);}
int ans[N];
int cnt;
void LargeSolve(int x) {
for(int i = 1; i <= n; ++i)
pre[i] = pre[i - 1] + (a[i] == x);
int tot = pre[n];
for(int i = 1; i <= n; ++i) {
int len = pos[i].size() - 1;
if(len == 0) continue;
cnt += len;
v[1] = 1 - (a[pos[i][1]] == x);
for(int j = 2; j < len; ++j)
v[j] = 1 - pre[pos[i][j]] + pre[pos[i][j - 1]];
update(ans[x], tot);
int mss = 0;
for(int j = 1; j <= len; ++j) {
if(mss > 0) mss += v[j];
else mss = v[j];
update(ans[x], mss + tot);
}
for(int j = 1; j <= len; ++j)
v[j] = pre[pos[i][j]] - pre[pos[i][j - 1]] - 1;
v[len + 1] = tot - pre[pos[i][len]] - 1;
mss = 0;
for(int j = 1; j <= len + 1; ++j) {
if(mss > 0) mss += v[j];
else mss = v[j];
update(ans[i], mss - (j <= len && a[pos[i][j]] == x) + 1 + len);
}
}
return;
}
void prework() {
for(int i = 1; i <= B; ++i) {
memset(buc, 0, sizeof buc);
int r = 0, flag = 0;
for(int l = 1; l <= n; ++l) {
while(!flag && r <= n) {
++buc[a[++r]];
if(buc[a[r]] == i) ++flag;
}
rpos[i][l] = r;
if(buc[a[l]] == i) --flag;
--buc[a[l]];
}
}
}
void SmallSolve(int x) {
const int len = pos[x].size() - 1;
for(int i = 0; i <= len; ++i) {
int t = 0;
for(int j = i + 1; j <= len; ++j) {
while(t < B && pos[x][j] - 1 >= rpos[t + 1][pos[x][i] + 1])
++t;
update(ans[x], i + (len - j + 1) + t);
}
}
int t = 0;
for(int i = len; i >= 0; --i) {
while(pos[x][i] < n && t < B && rpos[t + 1][pos[x][i] + 1] <= n) ++t;
update(ans[x], i + t);
}
}
void clear() {
memset(ans, 0, sizeof ans);
memset(buc, 0, sizeof buc);
memset(app, 0, sizeof app);
for(int i = 1; i <= n; ++i) pos[i].clear();
}
void solve() {
read(n);
clear();
for(int i = 1; i <= n; ++i)
read(a[i]), b[i] = a[i];
sort(b + 1, b + n + 1);
for(int i = 1; i <= n; ++i)
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
for(int i = 1; i <= n; ++i) pos[i].emplace_back(0);
for(int i = 1; i <= n; ++i)
++app[a[i]], pos[a[i]].emplace_back(i);
for(int i = 1; i <= n; ++i)
if(app[i] > B) LargeSolve(i);
prework();
for(int i = 1; i <= n; ++i)
if(app[i] <= B) SmallSolve(i);
int mx = 0;
for(int i = 1; i <= n; ++i)
update(mx, ans[i]);
printf("%d\n",mx);
for(int i = 1; i <= n; ++i)
if(ans[i] == mx) printf("%d\n",b[i]);
}
int main() {
freopen("mode.in","r",stdin);
freopen("mode.out","w",stdout);
int T; read(T);
while(T--) solve();
return 0;
}