题目链接: Equalize the Array
大致题意
给定一个长度为n的序列, 要求删除序列中尽可能少的元素, 使得剩余序列中出现的每一个元素数量相同.
解题思路
首先最终的序列, 一定是所有的元素都剩余x个. 那么在进行处理的时候, 原本有>x个的元素, 会把多余的部分删除, <x个的元素, 会全部都删除.
对于x, 这里要说明的是, 令集合st = { 所有元素出现次数(小到大排序过了) }, x一定是st中的某个元素. 这里用(num, cou)表示num这个数字出现cou次. 例如序列可以简化为 (3, 5), (4, 2), (1, 3), (9, 2). 那么一定满足 x ∈ { 2, 3, 5 };
证明: 不妨令 x = y (y ∈ st), 而 (y - 1) ∉ st && (y + 1) ∉ st.
①当x = y + 1时, 那么对于出现次数<y的部分不变, 对于=y的部分, 对结果的贡献会增加a, 对于>y的部分, 对结果的贡献会减少b, 最终对结果的影响为delta = a - b. 这样我们看似是无法确定a和b的关系的, 但是我们注意到, 如果在st中找到y的后继y’, 此时对结果的影响(相较于x = y的情况)记为delta’ = a - b’, 此时一定满足b’ >= b. 则我们实际会在x = y’处得到比x = y + 1的更优解.
②当x = y - 1时, 其实和情况①是相似的, 只是把两种情况对掉了, 我们会发现在y处取到更优解.
综上所述: 我们可以得出结论, x ∈ st;
此时我们考虑, 如果通过枚举x的方式, 枚举次数和st中的元素个数有关, 最坏情况: st = { 1, 2, 3, … }; 此时集合中的元素和为等差数列, 是n2级别的, 那么st中的元素个数就为√n级别.
在进行枚举x时, 对于当前x = y的情况, 此时要删除<y的所有元素, 和>y的元素多余的那部分.
我的做法: 我们不妨从大到小进行枚举x, 每次统计出现次数>=x的数字总数num, 最终都要使得他们变成x次, 相当于剩余元素的个数leave = x * num, 那么删除的元素个数del = n - leave. 是可以做到常数级别实现的.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define debug(a) cout << #a << " = " << a << endl;
using namespace std;
typedef long long ll;
map<int, int> mp1; //用于统计每个数字出现的次数.
map<int, int, greater<int>> mp2; //用于统计出现first次的数字有second个
int main()
{
int t; cin >> t;
while (t--) {
mp1.clear(), mp2.clear();
int n; scanf("%d", &n);
rep(i, n) {
int x; scanf("%d", &x);
mp1[x]++;
}
for (auto& [x, y] : mp1) mp2[y]++;
int res = 0x3f3f3f3f;
int bin = 0; //记录已经统计过的数字种类
for (auto& [cou, num] : mp2) {
bin += num;
int del = n - cou * bin;
res = min(res, del);
}
printf("%d\n", res);
}
return 0;
}