UVA - 1354 Mobile Computing: 枚举二叉树 位运算枚举子集,枚举子集的子集 回溯

题目点此跳转

There is a mysterious planet called Yaen, whose space is 2-dimensional. There are many beautiful stones on the planet, and the Yaen people love to collect them. They bring the stones back home and make nice mobile arts of them to decorate their 2-dimensional living rooms.
 In their 2-dimensional world, a mobile is defined recursively as follows:

 • a stone hung by a string, or
 • a rod of length 1 with two sub-mobiles at both ends; the rod is hung by a string at the center of gravity of sub-mobiles. When the weights of the sub-mobiles are n and m, and their distances from the center of gravity are a and b respectively, the equation n × a = m × b holds.
这里写图片描述
 For example, if you got three stones with weights 1, 1, and 2, here are some possible mobiles and
their widths:
这里写图片描述
 Given the weights of stones and the width of the room, your task is to design the widest possible
mobile satisfying both of the following conditions.

 • It uses all the stones.
 • Its width is less than the width of the room.

 You should ignore the widths of stones.
 In some cases two sub-mobiles hung from both ends of a rod might overlap (see the figure on the right). Such mobiles are acceptable. The width of the example is (1/3) + 1 + (1/4).
这里写图片描述

Input

The first line of the input gives the number of datasets. Then the specified number of datasets follow.
A dataset has the following format.
r
s
w1
.
.
.
ws
r is a decimal fraction representing the width of the room, which satisfies 0 < r < 10. s is the number of the stones. You may assume 1 ≤ s ≤ 6. wi is the weight of the i-th stone, which is an integer. You may assume 1 ≤ wi ≤ 1000.
 You can assume that no mobiles whose widths are between r−0.00001 and r+ 0.00001 can be made of given stones.

Output

For each dataset in the input, one line containing a decimal fraction should be output. The decimal fraction should give the width of the widest possible mobile as defined above. An output line should not contain extra characters such as spaces.
 In case there is no mobile which satisfies the requirement, answer ‘-1’ instead.
 The answer should not have an error greater than 0.00000001. You may output any numb er of digits after the decimal point, provided that the ab ove accuracy condition is satisfied.

Sample Input

5
1.3
3
1
2
1
1.4
3
1
2
1
2.0
3
1
2
1
1.59
4
2
1
1
3
1.7143
4
1
2
3
5

Sample Output

-1
1.3333333333333335
1.6666666666666667
1.5833333333333335
1.7142857142857142

思路

 题目意思是给出房间的宽度r和s个挂坠的重量w i 。设计一个尽量宽(但宽度不能超过房间宽度r)的天平,挂着所有挂坠。天平由一些长度为1的木棍组成。木棍的每一端要么挂一个挂坠,要么挂另外一个木棍。如图所示,设n和m分别是两端挂的总重量,要让天平平衡,必须满足n*a=m*b。

这题的关键点有三个地方。

  • 枚举二叉树:枚举子集,枚举子集的子集
  • 维护每个结点的左右长度
  • 多个过程值的回溯 -记录值到vector

下面分别处理一下这三个地方:

  1. 这里使用位运算去枚举子集,对于给定的位数s,子集可用 [ 0 , (1 << s) ] 来表示,每个数的值为0的位表示不取,值为1表示取那一位。
     那么(1 << s) - 1 就表示根结点(每一位都取),从此开始dfs,dfs过程对它的所有非空子集作为它的左子树进行dfs,而右子树只要取左子树的差集即可。
     于是这就涉及到了一个问题,当我取了一个子集之后,我在对它dfs的过程中,还要枚举此子集的所有子集,个人认为这是此题的知识点。枚举子集a的所有子集只要从其最大的真子集b(本身-1)开始,每一次(b-1)&a就是a的一个子集,直到b为0开始,这个读者可自行验证。
  2. 枚举子集的事情解决了之后就开始考虑如何记录每个结点的左右长度,我们可以先将当前结点的左右长度算出来,左右长度的比值是左边总重量与右边总重量的比值,所以一开始可以枚举子集将所有子集的重量和记录下来。然后再考虑其左右子树对它左右长度的影响,很显然,更新后的左长度等于原来裸的左长度加上左子树的左长度,右子树也一样。
     但是有一种情况要考虑,就是右子树的左子树可能会伸到最左边来,这个时候要比较一下取最大的。
     还要考虑房间的大小。 具体细节见代码。
  3. 最后就是多个过程值的回溯,对于某个子集,我每次dfs完它的一个左子树和右子树之后,我都需要对它的左右长度更新,而且我更新了之后应该立刻再往上更新,直到树结点,但是写dfs的时候好像不是这样的,是我dfs完了它的一个左子树和右子树然后更新左右值之后,我又要继续dfs它的另一个左子树和右子树,而最后传回其父结点的只是最后一种情况而已,这样显然是不对的,所以对每一个结点,都应该用一个vector数组保存它所dfs到的每一个左右子树的值,以备回溯到其父结点的时候使用。

代码

#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 1e3 + 100;
const int INF = 0x7fffffff;

int s, root;
double r, a[maxn], tot[maxn], ans;
vector<double> Left[maxn], Right[maxn];
bool vis[maxn];

bool isLeaf(int x) {
    int cnt = 0;
    for(int i = 0; i < s; ++i) if(x&(1<<i)) ++cnt;
    return cnt <= 1;
}

void dfs(int x) {
    if(vis[x]) return; vis[x] = 1;
    if(isLeaf(x)) { Left[x].push_back(0);Right[x].push_back(0); if(x==root) ans = max(ans, 0.0); return; }
    for(int i = (x-1)&x; i != 0; i = (i-1)&x) {
        int j = x^i;
        dfs(i); dfs(j);
        double L = tot[j]/tot[x];
        double R = tot[i]/tot[x];
        for(int u = 0; u < Left[i].size(); ++u)
        for(int v = 0; v < Left[j].size(); ++v) {
            double tL = max(Left[i][u]+L, Left[j][v]-R);
            double tR = max(Right[j][v]+R, Right[i][u]-L);
            if(tL+tR >= r) continue;
            Left[x].push_back(tL); Right[x].push_back(tR);
            if(x == root) ans = max(ans, tL+tR);
        }
    }
}

int main() {
    #ifdef _LOCAL
    IN;
    #endif // _LOCAL

    int t; cin >> t;
    while(t--) {
        scanf("%lf%d", &r, &s);
        for(int i = 0; i < s; ++i) scanf("%lf", &a[i]);
        met(tot, 0); met(Left, 0); met(Right, 0); ans = -1; met(vis, 0);
        for(int i = 0; i < (1<<s); ++i) {
            Left[i].clear(); Right[i].clear();
            for(int j = 0; j < s; ++j) if(i & (1<<j)) tot[i] += a[j];
        }
        root = (1<<s)-1;
        dfs(root);
        printf("%.10lf\n", ans);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值