【加拿大计算机竞赛 CCO 小行星采矿】题解

题目链接

Solution

首先将物品按照重量 m m m 进行升序排序,若重量相同,则按照权值 v v v 降序排序。

然后,物品的重量一定是这样分布的:

m 1 , m 1 , ⋯   , m 1 ∣ m 2 , m 2 , ⋯   , m 2 ∣ m 3 , m 3 , ⋯   , m 3 ∣ ⋯ (1) m_1, m_1, \cdots , m_1 \mid m_2, m_2, \cdots, m_2 \mid m_3, m_3, \cdots, m_3 \mid \cdots \tag{1} m1,m1,⋯,m1∣m2,m2,⋯,m2∣m3,m3,⋯,m3∣⋯(1)

其中 m 1 ∣ m 2 m_1 \mid m_2 m1∣m2 且 m 2 ∣ m 3 m_2 \mid m_3 m2∣m3,后续同理。

原问题 :在 ( 1 ) (1) (1) 的重量分布下,背包容量为 M M M,求最大价值。

于是我们可以知道,无论用何种选法,最终选中的物品总重量一定是 k m 1 ≤ M km_1 \leq M km1≤M,即

k ≤ ⌊ M m 1 ⌋ . k \leq \left\lfloor \frac{M}{m_1} \right\rfloor. k≤⌊m1M⌋.

这就启发我们将所有物品的重量都除以 m 1 m_1 m1,然后物品重量分布转化为:

1 , 1 , ⋯   , 1 ∣ m 2 ′ , m 2 ′ , ⋯   , m 2 ′ ∣ m 3 ′ , m 3 ′ , ⋯   , m 3 ′ ∣ ⋯ (2) 1, 1, \cdots , 1 \mid m_2', m_2', \cdots, m_2' \mid m_3', m_3', \cdots, m_3' \mid \cdots \tag{2} 1,1,⋯,1∣m2′,m2′,⋯,m2′∣m3′,m3′,⋯,m3′∣⋯(2)

其中 m i ′ = m i m 1 ( i = 1 , 2 , 3 , ⋯   ) m_i' = \dfrac{m_i}{m_1}\ (i = 1, 2, 3, \cdots) mi′=m1mi (i=1,2,3,⋯)。相应地,这个背包问题就可以转化。

新问题 :在 ( 2 ) (2) (2) 重量分布下,背包容量为 k k k,求最大价值。

同理,我们可以知道,除了选择的 1 1 1,剩下选择物品的总重一定是 m 2 ′ m_2' m2′ 的倍数。因此,设我们选了 k 1 k_1 k1 个 1 1 1, k 2 k_2 k2 个 m 2 ′ m_2' m2′,那么就有

那么就有 k 2 m 2 ′ ≤ k k_2m_2' \leq k k2m2′≤k,即

k 2 ≤ ⌊ k m 2 ′ ⌋ . k_2 \leq \left\lfloor \dfrac{k}{m_2'} \right\rfloor. k2≤⌊m2′k⌋.

我们把 k 2 k_2 k2 取到上界,剩下的部分 k − ⌊ k m 2 ′ ⌋ m 2 ′ = k mod m 2 ′ k - \left\lfloor \dfrac{k}{m_2'} \right\rfloor m_2' = k\ \text{mod}\ m_2' k−⌊m2′k⌋m2′=k mod m2′ 就只能被 k 1 k_1 k1 填满。

于是有

k 1 ≥ k mod m 2 ′ . k_1 \geq k \ \text{mod}\ m_2'. k1≥k mod m2′.

设 1 1 1 的数量有 c 1 c_1 c1 个。

那么我们可以首先取权值最大的 ( k mod m 2 ′ ) (k\ \text{mod}\ m_2') (k mod m2′) 个 1 1 1,然后剩下的 1 1 1 按照权值大小依次划分,分为 ⌈ max ⁡ ( 0 , c 1 − ( k mod m 2 ′ ) ) m 2 ′ ⌉ \left\lceil \dfrac{\max(0,\ c_1 -(k\ \text{mod}\ m_2'))}{m_2'} \right\rceil ⌈m2′max(0, c1−(k mod m2′))⌉ 组,每一组至多 有 m 2 ′ m_2' m2′ 个 1 1 1,的权值就是组内权值和。

然后将每一组视为一个新的物品,重量为 m 2 ′ m_2' m2′,权值 v v v 为组的权值。

QUESTIOIN: 如果最后一组的重量和不足 m 2 ′ m_2' m2′ 呢?

分类讨论。

  • 若 c 1 ≤ ( k mod m 2 ′ ) c_1 \leq (k\ \text{mod}\ m_2') c1≤(k mod m2′),没有分组的说法, 1 1 1 被全部用完。
  • 若 c 1 > ( k mod m 2 ′ ) c_1 > (k\ \text{mod}\ m_2') c1>(k mod m2′),根据我们的策略,先选 ( k mod m 2 ′ ) (k\ \text{mod}\ m_2') (k mod m2′) 个 1 1 1,那么剩下的重量 k − ( k mod m 2 ′ ) k - (k\ \text{mod}\ m_2') k−(k mod m2′) 一定是 m 2 ′ m_2' m2′ 的倍数。我们如果最终真的要选择这个重量不足 m 2 ′ m_2' m2′ 的组,将它补充到 m 2 ′ m_2' m2′ 也可以,即强制装满背包。

然后我们得到了这样的重量分布

m 2 ′ , m 2 ′ , ⋯   , m 2 ′ ∣ m 3 ′ , m 3 ′ , ⋯   , m 3 ′ ∣ ⋯ (3) m_2', m_2', \cdots, m_2' \mid m_3', m_3', \cdots, m_3' \mid \cdots \tag{3} m2′,m2′,⋯,m2′∣m3′,m3′,⋯,m3′∣⋯(3)

注意 m 2 ′ m_2' m2′ 的数量可能和 ( 2 ) (2) (2) 中不一样。

最新问题 :在 ( 3 ) (3) (3) 的重量分布下,背包容量为 k − ( k mod m 2 ′ ) k - (k\ \text{mod}\ m_2') k−(k mod m2′),求最大价值。

这相当于又回到了 ( 1 ) (1) (1),只是背包容量变了,所以重复 ( 1 ) (1) (1) 变成 ( 2 ) (2) (2) 的操作,然后用 ( 2 ) (2) (2) 的方式继续求解最新问题。

最后会剩下 m ′ , m ′ , ⋯   , m ′ m', m', \cdots, m' m′,m′,⋯,m′,我们贪心地按照权值从大到小选择。

时间复杂度 O ( n log ⁡ V ) O(n\log V) O(nlogV)

  • V V V 为值域上界。
  • 由 ( 1 ) (1) (1) 中的重量分布,以及 ∀ i \forall i ∀i 都有 m i + 1 m_{i + 1} mi+1 是 m i m_i mi 的倍数,且 m i ≠ m i + 1 m_i \neq m_{i + 1} mi=mi+1,得到 m i + 1 ≥ 2 m i m_{i + 1} \geq 2m_i mi+1≥2mi。所以物品重量最多有 log ⁡ V \log V logV 种。
  • 我们有 log ⁡ V \log V logV 次排序,每一次看似是 O ( n log ⁡ n ) O(n\log n) O(nlogn),但其实每一次将若干个 m i m_i mi 分组,组长度为 m i + 1 m_{i + 1} mi+1 时,都会使得长度减半,所以大概是 1 2 \dfrac{1}{2} 21 等比级数求和的数量级,可以看作 O ( n ) O(n) O(n)。

C++ Code

cpp 复制代码
#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int N;
    i64 M;
    std::cin >> N >> M;

    std::vector<std::array<i64, 2>> a(N);
    for (auto &[m, v]: a) {
        std::cin >> v >> m;
    }
    std::sort(a.begin(), a.end());

    std::vector<i64> val;
    val.reserve(N);

    i64 ans = 0;
    auto work = [&](i64 m1, i64 m2) {
        m2 /= m1;
        i64 r = M % m2;
        M /= m2;
        std::sort(val.begin(), val.end());
        while (not val.empty() and r-- > 0) {
            ans += val.back();
            val.pop_back();
        }
        if (val.empty()) {
            return;
        }
        std::vector<i64> nval;
        nval.reserve(N);
        while (not val.empty()) {
            i64 sum = 0;
            for (int i = 0; i < m2 and not val.empty(); i++) {
                sum += val.back();
                val.pop_back();
            }
            nval.push_back(sum);
        }
        val = std::move(nval);
    };
    i64 lst = 1;
    for (const auto &[m, v]: a) {
        if (lst < m) {
            work(lst, m);
            lst = m;
        }
        val.push_back(v);
    }
    std::sort(val.begin(), val.end());
    while (not val.empty() and M-- > 0) {
        ans += val.back();
        val.pop_back();
    }
    std::cout << ans << "\n";
    
    return 0;
}
相关推荐
ShineWinsu1 天前
对于C++:类和对象的解析—下(第二部分)
c++·面试·笔试·对象··工作·stati
2013092416271 天前
1968年 Hart, Nilsson, Raphael 《最小成本路径启发式确定的形式基础》A* 算法深度研究报告
人工智能·算法
如何原谅奋力过但无声1 天前
【力扣-Python-滑动窗口经典题】567.字符串的排列 | 424.替换后的最长重复字符 | 76.最小覆盖子串
算法·leetcode
BHXDML1 天前
第七章:类与对象(c++)
开发语言·c++
玄冥剑尊1 天前
贪心算法进阶
算法·贪心算法
玄冥剑尊1 天前
贪心算法深化 I
算法·贪心算法
52Hz1181 天前
力扣73.矩阵置零、54.螺旋矩阵、48.旋转图像
python·算法·leetcode·矩阵
BHXDML1 天前
第一章:线性回归& 逻辑回归
算法·逻辑回归·线性回归
yyf198905251 天前
C++ 跨平台开发的挑战与应对策略
c++
iAkuya1 天前
(leetcode)力扣100 二叉搜索树种第K小的元素(中序遍历||记录子树的节点数)
算法·leetcode·职场和发展