原题连接:UVA 1354
题目大意
题目分析
可以把挂坠和横放的木棍都看成结点,则整个天平就是一个二叉树,且每个结点要么是叶子结点要么是有2个孩子的内部结点,例如上图中的3种天平就对应于下图3个二叉树:
而且不同的天平之间可以重叠。同时,对于一棵确定的二叉树,可以计算出每个天平的确切位置,进而计算出整个天平的宽度。所以,本题的核心是:如何枚举出所有需要的二叉树
。下面介绍两种方法:
自底向上枚举
因为二叉树有s
个叶子结点,每个内部结点2个孩子,所以一共有s-1
个内部结点,即总共2s-1
个结点。
所以我们可以:
- 将初始的s个挂坠看成s个子树
- 每次选择两个挂坠形成新的子树加入,然后递归
s-1
次,则一共形成了2s-1
个结点 - 注意每次形成新子树的时候,都需要判断一下宽度是否溢出,若是则剪枝
- 例如以4个挂坠
{1,1,2,3}
为例,下面画出解答树的一部分:(每个结点所能形成的所有子树并未完全画出)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
// 自下而上的递归
const int maxs = 6; // 最多的挂坠数
const double EPS = 1E-9; // 最大误差精度
struct node {
double w; // 重量
double left, right; // 左右子树的最大宽度
node() :left(0.0), right(0.0), w(0) {
}
}Node[2 * maxs]; // s个叶子结点和s-1个内部结点,一共2s-1个结点
int vis[2 * maxs];
double r;
int s; // 宽度和挂坠数目
double maxr; // 求得的最大宽度
int isRight(node n) {
// 检验宽度是否合格
double width = n.left + n.right;
if (width <= r + EPS) return 1;
else return 0;
}
void init() {
maxr = -1;
memset(vis, 0, sizeof(vis));
cin >> r >> s;
for (int i = 0; i < s; i++) {
cin >> Node[i].w;
Node[i].left = Node[i].right = 0;
}
}
void dfs(int index) {
// 构造第index个结点 0-2s-2
if (index == 2 * s - 1)