UVA1354天平难题 枚举二叉树

题意:给出房间宽度r和s个挂坠的重量wi。设计一个尽量宽(但宽度不能超过房间宽度r)的天平,挂着所有挂坠。天平由一些长度为1的木棍组成。木棍的每一端要么挂一个挂坠,要么挂另一个木棍,设n和m分别是两端挂的总重量,要让天平平衡,必须满足n*a=m*b。(0<r<10,1\leqslants\leqslant6)。输出和标准答案的绝对误差不应该超过10^{-8}

分析:好了数值很小,可以暴力,问题是怎么枚举二叉树。这是本题的难点,很多人第一次做这种类型的题目时,都不知道怎么下手,当然我也是,考虑很久,也写不出枚举二叉树算法,看过解析才知道原来可以这么简单,才知道自己对二叉树的理解还是欠缺了。二叉树的核心在于结点可以组成节点。

下面以一个例子来说明:

4个挂坠,每个挂坠重量分别为1,2,3,5。房间宽度为1.7143。

                        3                                                                                                                                                                                                0.666|0.333                                                                                                                                                                                      -------------------                                                                                                                                                                                    |                      |                                                                                                                                                                                   1                     2

先将第一个节点和第二个节点组成一个节点,重量为3,左边宽度为0.6666,右边为0.3333,增加到节点列表中,节点列表为1,2,3,5,3。然后继续找下一层,找另外两个没找过的节点为3,5,组成节点,重量为8,增加到节点列表中,当前节点列表为1,2,3,5,3,8,访问列表为0,0,0,0,1,1。0表示访问过。所以继续下一层,然后就剩下组合节点3和8了,取3左边的长度为0.666,8右边的长度为0.375。相加为1.041再加上1就是2.041,超过房间宽度,那么这种情况就不符合,继续循环这次是8和3,取8的左边为0.625取3的右边0.3,还是超过,不符合,没有可访问节点,回溯。回溯到第二层,目前循环到刚刚是3和5,5访问过不符合,下面就继续访问节点4也就是重量3,是组合节点有其左右长度,分别为0.666和0.333,一开始是3和3取也就是节点2和节点4,取2节点左边长度为0,4节点右边长度为0.333,总长度为1.333,将节点6重量为6添加到节点列表中,节点列表为1,2,3,5,3,6,节点6左右长度分别是0.5和0.8333。可以进行下一层,访问列表为0,0,0,1,0,1。可访问的节点是3和6,然后取3的左边和6的右边,不符合,循环,取6的左边和3的右边为0.5+1<1.7041,符合条件,达到顶层,退出和当前最大比较。回溯下一个。以此类推可以访问过所有节点组成的所有二叉树。

代码部分:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include<iostream>
using namespace std;
const int maxn = 20;
double lw[maxn], rw[maxn], r; int w[maxn], vis[maxn]; int indexw = 0; int n; double ans;
void dfs(int layer) {
	if (layer == n)return;
	for (int i = 0; i < maxn; i++) {
		if (vis[i]) {
			for (int j = 0; j < maxn; j++) {
				if (i != j && vis[j]) {
					double L = max(lw[i], lw[j] - 1); double R = max(rw[j], rw[i] - 1);//防止右边的节点的左长度超过左边节点的左长度,同理右也是
					if (L + R + 1 < r) {//判断是否小于房间长度
						if (layer == n - 1)ans = max(ans, L + R + 1);
						vis[i] = vis[j] = 0;//置为访问过
						int id = indexw++;
						vis[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;
						dfs(layer + 1);
						vis[i] = vis[j] = 1;
						vis[--indexw] = 0;//新节点要置为不可访问!
					}
				}
			}
		}
	}
}
int main() {
	int kase;
	cin >> kase;
	while (kase-- > 0) {
		indexw = 0; ans = -1;
		memset(vis, 0, sizeof(w));
		memset(lw, 0, sizeof(lw));
		memset(rw, 0, sizeof(rw));
		cin >>r>> n;
		for (int i = 0; i < n; i++) { cin >> w[i]; vis[indexw++] = 1; }
		
		if (n == 1) { printf("0.0000000000\n"); continue; }
		dfs(1);
		printf("%.10lf\n", ans);
	}

	return 0;
}

 老师的代码是玄学(表示学不来)

// 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 point
  Tree():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)&subset; 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;
}

他用的是二进制枚举子集,每次都取出左右子集,枚举集合的所有子集,最后进行子集判断,果然厉害。

猜你喜欢

转载自blog.csdn.net/qq_36973725/article/details/83990588
今日推荐