1、题目
2、题意
给出房间的宽度 r r r 和 s s s 个挂坠的重量 w i w_i wi。设计一个尽量宽(但宽度不能超过房间宽度 r r r)的天平,挂着所有挂坠。
天平由一些长度为1的木棍组成。木棍的每一端要么挂一个挂坠,要么挂另外一个木棍。如图7-9所示,设 n n n 和 m m m 分别是两端挂的总重量,要让天平平衡,必须满足 n ∗ a = m ∗ b n*a=m*b n∗a=m∗b。
例如,如果有3个重量分别为1, 1, 2的挂坠,有3种平衡的天平,如图7-10所示。
挂坠的宽度忽略不计,且不同的子天平可以相互重叠。如图7-11所示,宽度为 ( 1 / 3 ) + 1 + ( 1 / 4 ) (1/3)+1+(1/4) (1/3)+1+(1/4)。
输入第一行为数据组数。每组数据前两行为房间宽度 r r r 和挂坠数目 s s s( 0 < r < 10 0<r<10 0<r<10, 1 ≤ s ≤ 6 1≤s≤6 1≤s≤6)。以下 s s s 行每行为一个挂坠的重量 w i ( 1 ≤ w i ≤ 1000 ) w_i(1≤w_i≤1000) wi(1≤wi≤1000)。输入保证不存在天平的宽度恰好在 r − 1 0 − 5 r-10 ^{-5} r−10−5 和 r + 1 0 − 5 r+10^{-5} r+10−5 之间(这样可以保证不会出现精度问题)。
对于每组数据,输出最优天平的宽度。如果无解,输出-1。你的输出和标准答案的绝对误差不应超过10-8。
3、分析
如果把挂坠和木棍都作为结点,则一个天平对应一棵二叉树,如题目中给出的,挂坠为1, 1, 2的3个天平如图7-12所示。
对于一棵确定二叉树,可以计算出每个挂坠的确切位置,进而计算出整个天平的宽度,所以本题的核心任务是:枚举二叉树。
如何枚举二叉树呢?最直观的方法是沿用回溯法框架,每次选择两个结点组成一棵子树,递归 s − 1 s-1 s−1层即可。以4个挂坠1, 1, 2, 3为例,下面是解答树的一部分(每个结点的子树并没有全部画出),如图7-13所示。
上面的方法已经足够解决本题,但还有优化的余地,因为有些二叉树被枚举了多次(如图7-13中的两个粗框结点)。
推荐的枚举方法是:自顶向下构造,每次枚举左子树用到哪个子集,则右子树就是使用剩下的子集。
4、代码实现
// UVa1354 Mobile Computing
// Rujia Liu
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;struct Tree {double L, R; // distance from the root to the leftmost/rightmost pointTree():L(0),R(0) {}
};const int maxn = 6;int n, vis[1<<maxn];
double r, w[maxn], sum[1<<maxn];
vector<Tree> tree[1<<maxn];void dfs(int subset) {if(vis[subset]) return;vis[subset] = true;bool have_children = false;for(int left = (subset-1)⊂ left; left = (left-1)&subset) {have_children = true;int right = subset^left;double d1 = sum[right] / sum[subset];double d2 = sum[left] / sum[subset];dfs(left); dfs(right);for(int i = 0; i < tree[left].size(); i++)for(int j = 0; j < tree[right].size(); j++) {Tree t;t.L = max(tree[left][i].L + d1, tree[right][j].L - d2);t.R = max(tree[right][j].R + d2, tree[left][i].R - d1);if(t.L + t.R < r) tree[subset].push_back(t);}}if(!have_children) tree[subset].push_back(Tree());
}int main() {int T;scanf("%d", &T);while(T--) {scanf("%lf%d", &r, &n);for(int i = 0; i < n; i++) scanf("%lf", &w[i]);for(int i = 0; i < (1<<n); i++) {sum[i] = 0;tree[i].clear();for(int j = 0; j < n; j++)if(i & (1<<j)) sum[i] += w[j];}int root = (1<<n)-1;memset(vis, 0, sizeof(vis));dfs(root);double ans = -1;for(int i = 0; i < tree[root].size(); i++)ans = max(ans, tree[root][i].L + tree[root][i].R);printf("%.10lf\n", ans);}return 0;
}