Mobile Computing-天平难题-Uva1354(回溯枚举二叉树)
原题:https://uva.onlinejudge.org/external/13/1354.pdf
有s块石头,每块都被一根绳子吊着,如果有两个及以上的石头,需要平衡的天平把所有的石头挂起来。
房间的宽度为r,问小于房间宽度r的天平的最大宽度。
分析: 是个回溯枚举的问题,枚举中途如果发现当前宽度已经大于r,回溯。
难点: 也可以说是亮点,就是枚举所有的二叉树,一个天平可以看成是一个二叉树。
具体点说递归建立二叉树的过程就是每次从包含所有节点的集合中选择两个节点,合二为一
所以我们建树的过程是先确定叶子节点,再合并生成父节点。
因为我们建立的二叉树的特点就是所有叶子节点是我们要挂的石头,所有父节点都是一个天平
细节:这题有个特别坑的细节,卡了我wa好久,就是二叉树的右节点可能会和左节点重叠
并且有可能右节点的左杠杆长度大于左节点的做杠杆长度,反之亦然。
上图
所以在生成父节点计算左右天平总长度的时候需要考虑上述特殊情况
#include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 12; int n, idx, f[MAXN], w[MAXN]; double r, ans, rw[MAXN], lw[MAXN]; void build_binary_tree(int dep) { if (dep == n) return; for (int i = 0; i < MAXN; i++) if (f[i]) for (int j = 0; j < MAXN; j++) if (i != j && f[j]) { double L = max(lw[i], lw[j] - 1), R = max(rw[i] - 1, rw[j]); if (1 + L + R < r) { if (dep == n - 1) ans = max(1 + L + R, ans); f[i] = f[j] = 0; int id = idx++; f[id] = 1; w[id] = w[i] + w[j]; lw[id] = w[j] * 1.0 / w[id] + L; rw[id] = w[i] * 1.0 / w[id] + R; build_binary_tree(dep + 1); f[--idx] = 0; f[i] = f[j] = 1; } } } int main() { int T; scanf("%d", &T); while (T--) { scanf("%lf%d", &r, &n); ans = -1; idx = 0; memset(f, 0, sizeof(f)); memset(rw, 0.0, sizeof(rw)); memset(lw, 0.0, sizeof(lw)); for (int i = 0; i < n; i++) { scanf("%d", &w[i]); f[idx++] = 1; } if (n == 1) {printf("0.0000000000\n"); continue;} build_binary_tree(1); printf("%.10lf\n", ans); } return 0; }