CF1446D1, D2 Frequency Problem
CF1446D1, D2 Frequency Problem
题目来源:Codeforces, Codeforces Round #683 (Div. 1, by Meet IT),CF1446D1 Frequency Problem (Easy Version),CF1446D2 Frequency Problem (Hard Version)
题目大意
有一个长度为 \(n\) 的序列 \(a_1,a_2,\dots ,a_n\)。
我们定义一个子段(连续子序列)是好的,当且仅当其中出现次数最多的元素不唯一。
请求出,最长的、好的子段的长度。
数据范围:\(1\leq n\leq 2\times10^5\)。对于 D1,\(1\leq a_i\leq \min(n,100)\),对于 D2,\(1\leq a_i\leq n\)。
本题题解
D1
如果原序列里,出现次数最多的数不唯一,则答案就是 \(n\)。否则,考虑那个唯一的、出现次数最多的元素,记为 \(x\)。
发现,最长的、好的子段(也就是答案),有个必要条件是:\(x\) 是其中出现次数最多的元素之一。
证明:考虑一个好的子段 \([l,r]\),如果 \(x\) 不是其中出现次数最多的元素。那么,我们依次考虑 \([l-1,r]\), \([l-2,r]\), ... ..., \([1,r]\), \([1,r+1]\), \([1,r+2]\), ... ..., \([1,n]\)。因为 \(x\) 的数量每次最多增加 \(1\) 个,且它是整个序列里出现次数最多的元素。那么总有某一步后,\(x\) 和序列里其他出现最多的元素一样多。
我们枚举,答案的子段里,和 \(x\) 出现次数一样多的元素是谁,记为 \(y\)。问题转化为,对一组数值 \((x,y)\) 计算答案。
我们可以忽略数列里除 \(x\), \(y\) 外的其他数。问题进一步转化为:求一个最长的子段,满足 \(x\), \(y\) 的出现次数一样多。
把数列里的 \(x\) 看做 \(1\),\(y\) 看做 \(-1\),其他数看做 \(0\)。得到一个序列 \(b\)。那么,我们要求最长的、和为 \(0\) 的子段长度。做前缀和,令 \(\text{sum}_i=\sum_{j=1}^{i}b_j\)。问题相当于找两个位置 \(0\leq l < r\leq n\),满足 \(\text{sum}_r = \text{sum}_l\)。对每个 \(\text{sum}\) 值 \(v\)(\([-n,n]\),共 \(2n+1\) 个值)记录它最早的出现位置。然后枚举 \(r\),并更新答案即可。
有读者可能对“忽略数列里除 \(x\), \(y\) 外的其他数”这一做法心存疑虑。因为有可能,我们找到的序列里,存在一个其他数值 \(z\),出现次数多于 \(x,y\)。但是,基于以下两点:
- 存在这样 "\(z\)" 的子段,长度一定小于答案。证明:我们按和上面同样的方法,向两边扩展一下,直到 \(x\) 和 \(z\) 出现次数相等。得到的序列仍然满足:存在两个数 \((x,z)\) 出现次数相等,且新的子段长度比原来长。所以只要存在这样的 "\(z\)",我们就扩展一下,最终一定能得到没有 "\(z\)" 的,也就是以 \(x\) 为出现次数最多的数的子段,且长度一定严格大于原来的子段。
- 答案的子段,一定会被考虑到。因为只要满足 \(x\), \(y\) 出现次数相等,就会被考虑到。
我们用这种方法,求出的就是答案。
时间复杂度 \(O(n\times\text{maxa})=O(n\times 100)\)。可以通过 D1。
D2
考虑答案序列里,出现次数最多的数,的出现次数。如果出现次数 \(>\sqrt{n}\),则满足条件的数值只有不超过 \(\sqrt{n}\) 个,我们套用 D1 的做法,时间复杂度 \(O(n\sqrt{n})\)。
如果出现最多的数,出现次数 \(\leq \sqrt{n}\),我们枚举这个出现次数,记为 \(t\)。然后做 two pointers。
具体来说,枚举区间的右端点 \(r\)。对每个 \(r\),记最小的,使得 \([l,r]\) 内所有数出现次数都 \(\leq t\) 的 \(l\) 为 \(L_r\)。容易发现,对于 \(r=1,2,\dots,n\),\(L_r\) 单调不降。并且,在当前的 \(t\) 下,对一个 \(r\) 来说,如果 \([L_r,r]\) 不是好的子段(出现次数为 \(t\) 的数值,不到 \(2\) 个),那么其他 \([l,r]\) (\(L_r<l\leq r\)) 也不可能是。因为出现次数为 \(t\) 的数值,只会更少。
所以,随着我们从小到大枚举 \(r\),只需要用一个变量来记录当前的 \(L_r\)。同时用数组维护一下,当前每个数的出现次数,以及每个出现次数的出现次数。就能确定,当前的 \([L_r,r]\) 是否是好的区间。具体可以见代码。这个 two pointers 的过程是 \(O(n)\) 的。因为有 \(\sqrt{n}\) 个 \(t\),所以这部分的时间复杂度也是 \(O(n\sqrt{n})\)。
总时间复杂度 \(O(n\sqrt{n})\)。
总结
本题首先需要一个结论。通过观察、分析题目性质,是可以想到的。
然后需要一个技巧:把一个普通序列,转化为只含 \(0\), \(1\), \(-1\) 的序列。这样就把复杂的、出现次数的关系,转化为了简单的数值关系。这个技巧在一些经典的二分中位数的题里也有用到(把 \(> \text{mid}\) 的视为 \(1\),\(\leq \text{mid}\) 的视为 \(-1\))。
D2,想到根号分治以后,应该就不是太难了。
参考代码
D1:
// problem: CF1446D1
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 2e5;
const int INF = 1e9;
int n, mxa, a[MAXN + 5];
int cnt[MAXN + 5], mp[MAXN * 2 + 5];
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
ckmax(mxa, a[i]);
cnt[a[i]]++;
}
int mx = 0, semx = 0;
for (int i = 1; i <= mxa; ++i) {
if (cnt[i] > cnt[mx]) {
semx = mx;
mx = i;
} else if (cnt[i] > cnt[semx]) {
semx = i;
}
}
// cerr << mx << " " << semx << endl;
if (!semx) {
// 整个序列只有一种值
cout << 0 << endl;
return 0;
}
if (cnt[semx] == cnt[mx]) {
cout << n << endl;
return 0;
}
int ans = 0;
for (int v = 1; v <= mxa; ++v) {
if (v == mx)
continue;
if (!cnt[v])
continue;
for (int i = 0; i <= n * 2; ++i) {
mp[i] = INF;
}
mp[n] = 0;
int presum = 0;
for (int i = 1; i <= n; ++i) {
int x = 0;
if (a[i] == mx) x = 1;
else if (a[i] == v) x = -1;
presum += x;
ckmax(ans, i - mp[presum + n]);
ckmin(mp[presum + n], i);
}
}
cout << ans << endl;
return 0;
}
D2:
// problem: CF1446D2
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 2e5;
const int INF = 1e9;
const int BOUND = 448;
int n, mxa, a[MAXN + 5];
int cnt[MAXN + 5], mp[MAXN * 2 + 5];
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
ckmax(mxa, a[i]);
cnt[a[i]]++;
}
int mx = 0, semx = 0;
for (int i = 1; i <= mxa; ++i) {
if (cnt[i] > cnt[mx]) {
semx = mx;
mx = i;
} else if (cnt[i] > cnt[semx]) {
semx = i;
}
}
// cerr << mx << " " << semx << endl;
if (!semx) {
// 整个序列只有一种值
cout << 0 << endl;
return 0;
}
if (cnt[semx] == cnt[mx]) {
cout << n << endl;
return 0;
}
int ans = 0;
for (int v = 1; v <= mxa; ++v) {
if (v == mx)
continue;
if (cnt[v] <= BOUND)
continue;
for (int i = 0; i <= n * 2; ++i) {
mp[i] = INF;
}
mp[n] = 0;
int presum = 0;
for (int i = 1; i <= n; ++i) {
int x = 0;
if (a[i] == mx) x = 1;
else if (a[i] == v) x = -1;
presum += x;
ckmax(ans, i - mp[presum + n]);
ckmin(mp[presum + n], i);
}
}
for (int frq = 1; frq <= BOUND; ++frq) {
for (int i = 1; i <= mxa; ++i) {
cnt[i] = 0;
}
for (int i = 1; i <= n; ++i) {
mp[i] = 0;
}
int l = 1;
for (int r = 1; r <= n; ++r) {
mp[cnt[a[r]]]--;
cnt[a[r]]++;
mp[cnt[a[r]]]++;
while (cnt[a[r]] > frq) {
mp[cnt[a[l]]]--;
cnt[a[l]]--;
mp[cnt[a[l]]]++;
++l;
}
if (mp[frq] >= 2) {
ckmax(ans, r - l + 1);
}
}
}
cout << ans << endl;
return 0;
}