1 条题解
-
0
问题 D. 切割数组(Cutting Out)
问题重述
你有一个长度为 的数组 。
你需要找到一个长度为 的数组 ,使得可以从 中完整切出尽可能多的 的副本。- 切出一个 的副本意味着:从 中取出 的每个元素(按值,不按顺序),每个元素用一次。
- 中允许重复元素。
- 目标是最大化可以切出的副本数。
核心思路
这个问题不能直接贪心,因为 的长度 固定,但 中每个数可以重复多次。
我们需要决定:最多能切出多少份(设份数为 ),然后根据 构造 。关键观察
- 如果我们能切出 份,那么对于某个数值 ,它在 中的出现次数为 ,则它在 中最多可以出现 次。
- 所有数值贡献的次数之和必须 ,才能构造出长度为 的 。
- 如果 可行,那么 也可行(单调性)。因此可以二分答案。
算法步骤
-
统计频率
用一个数组 记录每个数值在 中出现的次数。数值范围 。 -
二分最大份数
二分 的可能范围:。- 检查函数
can(x):
遍历所有出现过的数值 ,累加 。
若 ,则 可行(可以凑出长度 的 )。
- 检查函数
-
找到最大可行
二分结束后得到最大的 。 -
构造
再次遍历所有数值 ,将 重复 次加入答案数组,直到答案长度达到 。
复杂度分析
- 频率统计:, 为值域()。
- 二分: 次检查。
- 每次检查:遍历所有不同数值,最多 次,。
- 总复杂度:,足够快。
示例解析
示例 1
- 频率:
- 二分最大 :
- :$\lfloor 2/2 \rfloor + \lfloor 2/2 \rfloor + \lfloor 2/2 \rfloor + \lfloor 1/2 \rfloor = 1+1+1+0 = 3 \ge 3$ ✅
- :$\lfloor 2/3 \rfloor \times 3 + \lfloor 1/3 \rfloor = 0+0+0+0 = 0 < 3$ ❌
- 最大 。
- 构造 :
从每个数取 个:
取 各一个,得到 。
输出:
1 2 3
代码实现(C++)
#include <bits/stdc++.h> using namespace std; const int MAXV = 200005; int n, k; int cnt[MAXV]; vector<int> vals; // 存储出现过的数值 bool can(int x) { long long total = 0; for (int v : vals) { total += cnt[v] / x; } return total >= k; } int main() { cin >> n >> k; for (int i = 0; i < n; i++) { int x; cin >> x; if (cnt[x] == 0) vals.push_back(x); cnt[x]++; } // 二分最大份数 int lo = 1, hi = n, ans = 1; while (lo <= hi) { int mid = (lo + hi) / 2; if (can(mid)) { ans = mid; lo = mid + 1; } else { hi = mid - 1; } } // 构造答案 t vector<int> res; for (int v : vals) { int take = cnt[v] / ans; while (take-- && res.size() < k) { res.push_back(v); } } for (int i = 0; i < k; i++) { cout << res[i] << " "; } cout << endl; return 0; }
易错点提醒
- 二分边界:最大可能份数是 (当 且所有元素相同时)。
- 检查函数
can(x):累加可能超过 ,但只需判断是否 ,不需要精确值。 - 构造时:只需取前 个,不必完全用完所有可用次数。
- 数值范围:频率数组开到 足够。
- 1
信息
- ID
- 6418
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者