【加拿大计算机竞赛 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;
}
相关推荐
Ghost-Silver2 小时前
2025年度总结
开发语言·数据结构·c++·算法
yyy(十一月限定版)2 小时前
C++基础
java·开发语言·c++
谈笑也风生2 小时前
经典算法题型之排序算法(四)
数据结构·算法·排序算法
AI科技星2 小时前
空间螺旋电磁耦合常数 Z‘:拨开迷雾,让电磁力变得直观易懂
服务器·人工智能·科技·算法·生活
亚伯拉罕·黄肯2 小时前
强化学习算法笔记
笔记·算法
only-qi2 小时前
LeetCode 148. 排序链表
算法·leetcode·链表
岁岁的O泡奶2 小时前
NSSCTF_crypto_[SWPUCTF 2023 秋季新生赛]dpdp
经验分享·python·算法·密码学
smj2302_796826522 小时前
解决leetcode第3791题.给定范围内平衡整数的数目
python·算法·leetcode
不能只会打代码2 小时前
力扣--1970. 你能穿过矩阵的最后一天(Java)
java·算法·leetcode·二分查找·力扣·bfs·最后可行时间