CF2123E MEX Count 题解
题目简述
给定一个非负整数数组 a,我们定义数组的 MEX(最小排除值)为数组中未出现的最小非负整数。
例如:
MEX([2,2,1]) = 0MEX([3,1,0,1]) = 2MEX([0,3,1,2]) = 4
题目要求:对于每个 k = 0..n,统计删除恰好 k 个元素后,数组可能的不同 MEX 数量。
核心思路
直接枚举删除元素组合来求 MEX 是非常复杂的,我们换个角度:
-
统计每个数字出现次数
- 维护一个频率数组
cnt[i],记录每个数字的出现次数
- 维护一个频率数组
-
找到原数组 MEX
- 遍历 0..n 找第一个缺失的整数,记为
MEX_orig
- 遍历 0..n 找第一个缺失的整数,记为
-
枚举可能的 MEX
-
从原 MEX 向下枚举每个可能的 MEX = i
-
对每个 MEX=i,把数组元素分为三类:
- 必删元素:值等于 i,必须删除,数量 =
cnt[i] - 不可删元素:值 < i,每个至少保留一个,否则 MEX 会变小
- 可删可不删元素:值 < i 的重复 + 大于 i 的元素,数量固定,可以任意选择删除
- 必删元素:值等于 i,必须删除,数量 =
-
-
确定删除元素个数区间
-
删除元素总数 k = 必删数量 + 可删可不删部分
-
因此对每个 MEX=i,合法删除数量 k 构成 连续区间
[L,R]L = cnt[i](必须删除数量)R = n - i(必删 + 可删可不删数量)
-
-
用差分数组统计区间贡献
-
对每个 MEX=i 的区间
[L,R]:diff[L] += 1; diff[R+1] -= 1; -
前缀和展开后,得到每个 k 的可能 MEX 数量
-
代码实现(C++)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 55;
int n, a[N], b[N], c[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--) {
cin >> n;
// 读入数组并统计频率
for(int i = 1; i <= n; i++) {
cin >> a[i];
b[a[i]]++;
}
// 找原数组 MEX
int MEX = 0;
for(int i = 0; i <= n; i++) {
if(b[i] == 0) {
MEX = i;
break;
}
}
// 枚举可能的 MEX,从原 MEX 向下到 0
for(int i = MEX; i >= 0; i--) {
int L = b[i]; // 必须删除数量
int R = n - i; // 必删 + 可删可不删数量
c[L]++; // 区间开始,贡献 +1
c[R+1]--; // 区间结束,撤销贡献
}
// 前缀和展开,得到每个 k 的答案
for(int i = 0; i <= n; i++) {
if(i >= 1) c[i] += c[i-1];
cout << c[i] << " ";
}
cout << "\n";
// 清空数组,为下一组测试用例做准备
for(int i = 0; i <= n+1; i++)
a[i] = b[i] = c[i] = 0;
}
return 0;
}
总结
-
核心技巧:将每个 MEX 对应的删除数量转化为连续区间,利用差分数组 + 前缀和统计每个 k 的贡献
-
复杂度:O(n) per 测试用例
-
差分数组的作用是:
- 不需要枚举
[L,R]内每个 k - 只记录区间开始/结束,前缀和展开自动得到每个 k 的答案
- 不需要枚举
-
这道题比直接枚举组合简单得多,关键在于理解:
- MEX 的连续性
- 差分数组的区间加法
浙公网安备 33010602011771号