P8330 [ZJOI2022] 众数
小清新 DS 题。
首先是众数,那首选是根号算法,因为区间众数的常见做法是带根号的。下面默认序列是离散化过的。
先转化一下题目,相当于从序列里选一个区间 \([l, r]\),使得 \([l, r]\) 中众数的出现次数加上 \([l, r]\) 外众数的出现次数最大,并求出所有能作为某个取到最大的区间 \([l, r]\) 的外面的众数,因为我们会将 \([l, r]\) 内的众数变成外面的众数。
那么要求所有众数,告诉我们枚举众数的值是不能少的,那么就需要求出现次数,而出现次数往往和根号分治有关,这启发我们根号分治,设阈值为 \(B\)。
对于出现次数 \(>B\) 的众数,个数是 \(\mathcal{O}(\frac{n}{B})\) 级别的,所以可以枚举这个数 \(i\),并且对于每个 \(i\),我们可以 \(\mathcal{O}(n)\) 地求解。考虑求出 \(i\) 出现次数的前缀和,并枚举 \([l, r]\) 内的众数 \(j\),将所有 \(a_k = i\) 的位置设为 \(-1, a_k = j\) 的位置设为 \(1\),那么我们要的 \([l, r]\) 就是最大子段和所对应的区间,此时的答案就是最大子段和加上 \(i\) 的出现次数,最大子段和可以贪心地 \(\mathcal{O}(n)\) 求解。
这部分复杂度为 \(\mathcal{O}(\frac{n^2}{B})\)。
然后只需考虑内外出现次数都 \(< B\) 的了。仍然考虑枚举 \(i\),这时如果选择 \([l, r]\),那么答案就是 \([l, r]\) 内众数的出现次数减去 \([l, r]\) 内 \(i\) 的出现次数,再加上全局 \(i\) 的出现次数。注意到对于 \(i\) 来说,肯定有 \(a_{r + 1} = i, a_{l - 1} = i\),不然肯定不优(还有一种可能是 \(l = 1\) 或 \(r = n\))。所以对于所有 \(i\),合法的区间总计是 \(\mathcal{O}(B^2)\) 个。
那么我们就可以枚举 \(r\),考虑从左往右扫描线,并枚举 \(l\),那么此时只有 \([l, r]\) 的众数难以计算,正常来说众数是一定要根号的,看似不行了,但是这里有一个性质,就是我们考虑的每个数的出现次数都 \(< B\),于是众数的出现次数一定 \(< B\),这启发我们直接定义 \(M_i\) 表示 \([i, r]\) 的众数出现次数,那么 \(M_i \le B, \sum M_i \le nB\),另外 \(M_i \ge M_{i + 1}\),所以每次 \(r \leftarrow r + 1\) 时暴力更新即可,每次 \(M_i\) 至少加一,所以时间复杂度有保证。
注意我们需要翻转过来再算一遍,考虑 \(r = n\) 的情况。
这部分时间复杂度 \(\mathcal{O}(nB)\)。
平衡一下即可做到 \(\mathcal{O}(n\sqrt n)\),第二部分常数较大,实际情况 \(B\) 开的较小会更快。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 2e5 + 10, B = 200, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
namespace Loop1st {
int n, a[N], b[N], s[N], sz[N], ans[N], M[N], cnt[N];
vector<int>pos[N];
void cmax(int &x, int y) { if (x < y) x = y; }
void calc() {
for (int i = 1; i <= n; i++) M[i] = cnt[i] = 0;
for (int i = 1; i <= n; i++) if (pos[a[i]].size() < B) {
int x = a[i]; // x 即文中的 i
for (int j = cnt[x]; ~j; j--) {
int l = j ? pos[x][j - 1] + 1 : 1, r = pos[x][j]; // 注意这里右端点是 i 不是 r, r 只用来更新 M
cmax(ans[x], sz[x] + M[l] - (cnt[x] - j));
while (l <= r && M[r] < cnt[x] - j + 1) M[r] = cnt[x] - j + 1, r--;
}
cnt[x]++;
}
}
void main() {
cin >> n;
for (int i = 1; i <= n; i++) pos[i].clear(), ans[i] = sz[i] = 0;
for (int i = 1; i <= n; i++) cin >> 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, sz[a[i]]++, pos[a[i]].push_back(i);
for (int i = 1; i <= n; i++) if (pos[i].size() >= B) {
fill(s + 1, s + n + 1, 0);
for (int j = 1; j <= n; j++) s[j] = s[j - 1] + (a[j] == i);
for (int j = 1; j <= n; j++) if (i != j) {
int las = 0, cur = 0;
for (int k : pos[j]) {
cur = max(1, cur - s[k] + s[las] + 1);
cmax(ans[i], sz[i] + cur);
las = k;
}
las = cur = 0;
for (int k : pos[j]) {
cur = max(s[k] - s[las], cur + s[k] - s[las]);
cmax(ans[j], sz[j] + cur);
cur--; las = k;
}
}
}
calc();
reverse(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
reverse(pos[i].begin(), pos[i].end());
for (int &x : pos[i]) x = n - x + 1;
}
calc();
int res = 0;
for (int i = 1; i <= n; i++) cmax(res, ans[i]);
cout << res << '\n';
for (int i = 1; i <= n; i++) if (res == ans[i]) cout << b[i] << '\n';
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T = 1;
cin >> T;
while (T--) Loop1st::main();
return 0;
}
// start coding at 19:40
// finish debugging at 20:13

浙公网安备 33010602011771号