【加拿大计算机竞赛 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;
}
相关推荐
软件算法开发17 分钟前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化
Thera77730 分钟前
C++ 高性能时间轮定时器:从单例设计到 Linux timerfd 深度优化
linux·开发语言·c++
superior tigre1 小时前
22 括号生成
算法·深度优先
君义_noip2 小时前
信息学奥赛一本通 1952:【10NOIP普及组】三国游戏 | 洛谷 P1199 [NOIP 2010 普及组] 三国游戏
c++·信息学奥赛·csp-s
努力也学不会java2 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
旖-旎2 小时前
二分查找(x的平方根)(4)
c++·算法·二分查找·力扣·双指针
ECT-OS-JiuHuaShan3 小时前
朱梁万有递归元定理,重构《易经》
算法·重构
顶点多余3 小时前
使用C/C++语言链接Mysql详解
数据库·c++·mysql
汉克老师3 小时前
GESP2026年3月认证C++四级( 第二部分判断题(1-10))
c++·指针·函数重载·文件操作·数组·gesp4级·gesp四级
智者知已应修善业3 小时前
【51单片机独立按键控制数码管移动反向,2片74CH573/74CH273段和位,按键按下保持原状态】2023-3-25
经验分享·笔记·单片机·嵌入式硬件·算法·51单片机