题目链接
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;
}