1 条题解

  • 0
    @ 2026-4-5 21:39:04

    问题 D. 切割数组(Cutting Out)

    问题重述

    你有一个长度为 nn 的数组 ss
    你需要找到一个长度为 kk 的数组 tt,使得可以从 ss完整切出尽可能多的 tt 的副本。

    • 切出一个 tt 的副本意味着:从 ss 中取出 tt 的每个元素(按值,不按顺序),每个元素用一次。
    • tt 中允许重复元素。
    • 目标是最大化可以切出的副本数。

    核心思路

    这个问题不能直接贪心,因为 tt 的长度 kk 固定,但 tt 中每个数可以重复多次。
    我们需要决定:最多能切出多少份(设份数为 xx),然后根据 xx 构造 tt

    关键观察

    • 如果我们能切出 xx 份,那么对于某个数值 vv,它在 ss 中的出现次数为 cnt[v]cnt[v],则它在 tt 中最多可以出现 cnt[v]x\lfloor \frac{cnt[v]}{x} \rfloor 次。
    • 所有数值贡献的次数之和必须 k\ge k,才能构造出长度为 kktt
    • 如果 xx 可行,那么 x1x-1 也可行(单调性)。因此可以二分答案

    算法步骤

    1. 统计频率
      用一个数组 cntcnt 记录每个数值在 ss 中出现的次数。数值范围 2105\le 2 \cdot 10^5

    2. 二分最大份数
      二分 xx 的可能范围:[1,n][1, n]

      • 检查函数 can(x)
        遍历所有出现过的数值 vv,累加 total=cnt[v]xtotal = \sum \lfloor \frac{cnt[v]}{x} \rfloor
        totalktotal \ge k,则 xx 可行(可以凑出长度 kktt)。
    3. 找到最大可行 xx
      二分结束后得到最大的 xmaxx_{max}

    4. 构造 tt
      再次遍历所有数值 vv,将 vv 重复 cnt[v]xmax\lfloor \frac{cnt[v]}{x_{max}} \rfloor 次加入答案数组,直到答案长度达到 kk


    复杂度分析

    • 频率统计:O(n+M)O(n + M)MM 为值域(21052 \cdot 10^5)。
    • 二分:logn18\log n \approx 18 次检查。
    • 每次检查:遍历所有不同数值,最多 MM 次,O(M)O(M)
    • 总复杂度:O((n+M)logn)O((n + M) \log n),足够快。

    示例解析

    示例 1

    s=[1,2,3,2,4,3,1], k=3s = [1,2,3,2,4,3,1],\ k=3

    • 频率:1:2, 2:2, 3:2, 4:11:2,\ 2:2,\ 3:2,\ 4:1
    • 二分最大 xx
      • x=2x=2:$\lfloor 2/2 \rfloor + \lfloor 2/2 \rfloor + \lfloor 2/2 \rfloor + \lfloor 1/2 \rfloor = 1+1+1+0 = 3 \ge 3$ ✅
      • x=3x=3:$\lfloor 2/3 \rfloor \times 3 + \lfloor 1/3 \rfloor = 0+0+0+0 = 0 < 3$ ❌
    • 最大 x=2x=2
    • 构造 tt
      从每个数取 cnt/2\lfloor cnt / 2 \rfloor 个:
      1,2,31,2,3 各一个,得到 t=[1,2,3]t=[1,2,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;
    }
    

    易错点提醒

    1. 二分边界:最大可能份数是 nn(当 k=1k=1 且所有元素相同时)。
    2. 检查函数 can(x):累加可能超过 kk,但只需判断是否 k\ge k,不需要精确值。
    3. 构造时:只需取前 kk 个,不必完全用完所有可用次数。
    4. 数值范围:频率数组开到 200005200005 足够。

    • 1

    信息

    ID
    6418
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    4
    已通过
    1
    上传者