1 条题解
-
0
题目概述
我们有一个长度为 的一维空间,以及 个物品,每个物品长度为 。物品按编号顺序依次尝试放入空间:
- 如果存在一段长度至少为 的连续空余空间,则必须放入
- 否则跳过该物品
要求:找出所有可能的空间占用长度(即成功放入的物品总长度)。
问题分析
核心难点
- 物品必须按顺序尝试放入
- 对于同一个物品集合,不同的放置顺序可能导致不同的占用长度
- 但 ,需要高效算法
关键观察
- 放置顺序的重要性:由于物品必须按编号顺序尝试,放置结果与物品的选择和排列都有关
- 状态压缩: 提示我们可以使用状态压缩DP
- 可行性判断:对于选中的物品集合,需要判断是否存在一种放置方式使得所有选中的物品都能成功放入
算法思路
总体框架
枚举所有可能的物品子集 ,判断该子集能否全部放入空间,如果能则记录总长度 。
核心函数
work(S)判断物品集合 是否能全部放入空间:
-
预处理:
- 提取 中的物品编号到数组
- 初始化 为第一个不能放入的物品长度(或 )
-
动态规划:
- 状态 :放置集合 ( 是 的子集)后,剩余的最小可用空间长度
- 转移:考虑最后一个放置的物品,枚举之前的放置状态
- 关键:,并考虑编号约束
-
可行性判断:
- 如果 ,说明集合 可以全部放入
- 否则不能全部放入
状态转移详解
对于状态 ,设 是 中最后一个物品(编号最大的):
- 枚举 中除去 的子集
- ,这里利用了空间可以分段利用的性质
- 然后加上物品 的长度,并考虑编号约束
编号约束处理
由于物品必须按编号顺序尝试,如果某个编号较小的物品没有被选中,它可能阻碍后续放置。代码中通过:
for (int j = 0; j < b[x]; j++) if (!(S >> j & 1)) f[s] = std::min(f[s], a[j]);来确保放置方案符合顺序要求。
算法步骤
步骤1:预处理
// 计算每个子集的总长度 for (int s = 1; s < (1 << n); s++) w[s] = w[s ^ lowbit(s)] + a[pos[lowbit(s)]];步骤2:枚举所有子集
for (int s = 1; s < (1 << n); s++) if (work(s)) // 判断子集s能否全部放入 ans.push_back(w[s]);步骤3:输出结果
std::sort(ans.begin(), ans.end()); ans.erase(std::unique(ans.begin(), ans.end()), ans.end()); std::cout << ans.size() << '\n'; for (ll x : ans) std::cout << x << ' ';
正确性分析
为什么这样能找出所有可能?
- 枚举了所有物品子集
- 对每个子集,精确判断是否能按规则全部放入
- 记录所有可行子集的总长度
复杂度分析
- 子集枚举:
- 每个子集的DP:
- 总复杂度:$\sum_{k=0}^n \binom{n}{k} \cdot 2^k \cdot k = O(3^n \cdot n)$
- 对于 ,,可以接受
样例解析
样例1
输入:8 4 3 4 1 2 输出:4 4 6 7 8可能的情况:
- 只放入物品2(长度1)和物品4(长度2):总长3?不对,重新分析...
实际上算法会找到所有可行的子集:
- 子集 {1,2}:总长4
- 子集 {1,3,4}:总长6
- 子集 {1,2,3,4}:总长7?不对,应该是8?
- 等等
最终输出4种不同的占用长度。
算法亮点
- 状态压缩:利用n小的特点,枚举所有子集
- 动态规划:在子集内进行DP,判断可行性
- 顺序处理:巧妙处理物品必须按编号顺序尝试的约束
- 空间优化:使用bitset和lowbit优化状态处理
总结
这道题的核心在于:
- 理解放置规则:物品必须按顺序尝试,存在时必须放入
- 子集枚举:由于n小,可以枚举所有可能的物品选择方案
- 可行性判断:对每个子集,通过DP判断是否能按规则全部放入
- 结果去重:记录所有可行子集的总长度,排序去重
这种"子集枚举 + 内部DP"的两层结构,在解决组合优化问题时非常有效,特别是当问题规模允许枚举所有子集时。
- 1
信息
- ID
- 4793
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者