UVa 1354 Mobile Computing
题目分析
问题描述
在一个二维星球上,人们用石头制作移动雕塑(mobile\texttt{mobile}mobile)。一个 mobile\texttt{mobile}mobile 的定义如下:
- 一个石头用一根绳子悬挂;或者
- 一根长度为 111 的杆,两端各悬挂一个子 mobile\texttt{mobile}mobile,杆悬挂在两个子 mobile\texttt{mobile}mobile 的整体重心处。
当两个子 mobile\texttt{mobile}mobile 的重量分别为 nnn 和 mmm,它们距离重心的距离分别为 aaa 和 bbb 时,满足力矩平衡方程:
n×a=m×bn \times a = m \times bn×a=m×b
给定石头的重量和房间的宽度,要求设计一个使用所有石头的 mobile\texttt{mobile}mobile,其宽度小于房间宽度,并且要使得这个宽度尽可能大。
输入格式
- 第一行:数据集数量 TTT
- 每个数据集:
- 房间宽度 rrr(0<r<100 < r < 100<r<10)
- 石头数量 sss(1≤s≤61 \leq s \leq 61≤s≤6)
- sss 个石头的重量 wiw_iwi(1≤wi≤10001 \leq w_i \leq 10001≤wi≤1000)
输出格式
对于每个数据集,输出可能的最大宽度(精度误差不超过 10−810^{-8}10−8),如果没有满足条件的 mobile\texttt{mobile}mobile,输出 −1-1−1。
解题思路
关键观察
-
力矩平衡条件:当合并两个子树时,设左子树重量为 nnn,右子树重量为 mmm,则左子树重心到整体重心的距离为:
a=mn+ma = \frac{m}{n + m}a=n+mm
右子树重心到整体重心的距离为:
b=nn+mb = \frac{n}{n + m}b=n+mn -
宽度计算:mobile\texttt{mobile}mobile 的总宽度等于悬挂点到最左端的距离加上悬挂点到最右端的距离。
-
递归结构:每个子树可以用一个三元组 (weight,left,right)(weight, left, right)(weight,left,right) 表示:
- weightweightweight:子树的总重量
- leftleftleft:子树重心到最左端的距离
- rightrightright:子树重心到最右端的距离
算法设计
由于 s≤6s \leq 6s≤6,我们可以使用状态压缩动态规划结合深度优先搜索:
-
状态表示:用位掩码 maskmaskmask 表示使用的石头集合
-
状态存储:dp[mask]dp[mask]dp[mask] 存储该集合能形成的所有 (weight,left,right)(weight, left, right)(weight,left,right) 三元组
-
初始状态:单个石头 iii 的状态为 (wi,0,0)(w_i, 0, 0)(wi,0,0)
-
状态转移:对于每个 maskmaskmask,枚举其所有非空真子集 leftMaskleftMaskleftMask,计算:
- rightMask=mask⊕leftMaskrightMask = mask \oplus leftMaskrightMask=mask⊕leftMask
- 对于 dp[leftMask]dp[leftMask]dp[leftMask] 和 dp[rightMask]dp[rightMask]dp[rightMask] 中的每对三元组,计算合并后的新状态
-
合并计算:
- 总重量:weight=L.weight+R.weightweight = L.weight + R.weightweight=L.weight+R.weight
- 距离:a=R.weightweighta = \frac{R.weight}{weight}a=weightR.weight, b=L.weightweightb = \frac{L.weight}{weight}b=weightL.weight
- 左半宽度:leftWidth=max(a+L.left,R.left−b)leftWidth = \max(a + L.left, R.left - b)leftWidth=max(a+L.left,R.left−b)
- 右半宽度:rightWidth=max(b+R.right,L.right−a)rightWidth = \max(b + R.right, L.right - a)rightWidth=max(b+R.right,L.right−a)
复杂度分析
- 状态数:2s=642^s = 642s=64
- 每个状态的三元组数量在最坏情况下可能达到 C(s)C(s)C(s)(卡特兰数),但由于 s≤6s \leq 6s≤6,实际可接受
- 总体复杂度在合理范围内
代码实现
// Mobile Computing
// UVa ID: 1354
// Verdict: Accepted
// Submission Date: 2025-10-23
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;// 表示一个子树的状态:重量、左半宽度、右半宽度
struct Node {double weight, left, right;Node(double w, double l, double r) : weight(w), left(l), right(r) {}
};vector<Node> dp[1 << 6]; // dp[mask] 存储该掩码对应的所有可能子树状态
int s; // 石头数量
vector<int> w; // 石头重量
double room; // 房间宽度
bool visited[1 << 6]; // 标记状态是否已计算// 深度优先搜索计算所有可能的子树组合
void dfs(int mask) {if (visited[mask]) return; // 已计算过,直接返回visited[mask] = true;// 单个石头的情况if (__builtin_popcount(mask) == 1) {int i = __builtin_ctz(mask); // 找到最低位的 1 对应的石头索引dp[mask].push_back(Node(w[i], 0.0, 0.0));return;}// 枚举所有非空真子集进行合并for (int leftMask = (mask - 1) & mask; leftMask > 0; leftMask = (leftMask - 1) & mask) {int rightMask = mask ^ leftMask; // 补集作为右子树dfs(leftMask); // 计算左子树dfs(rightMask); // 计算右子树// 合并左右子树的所有可能组合for (const Node& L : dp[leftMask]) {for (const Node& R : dp[rightMask]) {double totalWeight = L.weight + R.weight;double a = R.weight / totalWeight; // 左子树重心到整体重心的距离double b = L.weight / totalWeight; // 右子树重心到整体重心的距离// 计算新的左右半宽度double leftWidth = max(a + L.left, R.left - b);double rightWidth = max(b + R.right, L.right - a);// 添加新状态dp[mask].push_back(Node(totalWeight, leftWidth, rightWidth));}}}
}int main() {int T;cin >> T;while (T--) {cin >> room;cin >> s;w.resize(s);for (int i = 0; i < s; i++) cin >> w[i];int total = (1 << s) - 1; // 所有石头的位掩码// 初始化for (int i = 0; i <= total; i++) {dp[i].clear();visited[i] = false;}// 计算所有可能的组合dfs(total);// 寻找满足条件的最大宽度double best = -1.0;for (const Node& nd : dp[total]) {double width = nd.left + nd.right;if (width < room - 1e-9) { // 考虑浮点精度误差if (width > best) best = width;}}// 输出结果if (best < 0) {cout << "-1\n";} else {cout << fixed << setprecision(15) << best << "\n";}}return 0;
}
总结
本题通过状态压缩动态规划枚举所有可能的子树组合,利用力矩平衡原理和几何关系计算 mobile\texttt{mobile}mobile 的宽度。关键在于将每个子树抽象为 (weight,left,right)(weight, left, right)(weight,left,right) 三元组,这样无论子树内部结构多复杂,都能正确计算出合并后的宽度。算法充分利用了 s≤6s \leq 6s≤6 的小数据范围,通过位运算高效枚举所有可能状态。
