UVa 1354 天平难题 枚举二叉树

题意:给出房间宽度 r 和 s 个挂坠的重量 wi,设计一个尽量宽的天平,挂着所有挂坠。天平由一些长度为 1 的木棍组成,木棍的每一端要么挂一个挂坠,要么挂另外一个木棍。

这题卡了很久,看了很多大神的代码,终于把细节都搞懂了。

将挂坠所有可能的集合的重量算出来,再用二进制的方式枚举子集(左子树)和补集(右子树)构成二叉树,算出左右的宽度。

代码:

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

const int MAX = 6;

struct Tree{
    double L, R;    //树的左边和右边的长度
    Tree():L(0), R(0){}
};

double width, w[MAX], sum[1<<MAX];
int vis[1<<MAX];
vector<Tree> tree[1<<MAX];        //枚举出来的结点可以组合成多个树

void dfs(int subset);        //枚举二叉树

int main(){
    freopen("input.txt", "r", stdin);
    int T;
    cin >> T;
    
    while(T--){
        int s;
        scanf("%lf%d", &width, &s);
        for(int i=0; i<s; i++){
            scanf("%lf", &w[i]);
        }
        
        //初始化 
        memset(sum, 0, sizeof(sum));
        for(int i=1; i<(1<<s); i++){
            tree[i].clear();    //清空树
            int k = 1;
            for(int j=0; j<s; j++){    //算出当前集合所有结点的重量和
                if(k & i){
                    sum[i] += w[j];
                }
                k = (k << 1);
            } 
        }
        
        //进行枚举
        int root = (1<<s) - 1;
        memset(vis, 0, sizeof(vis));
        dfs(root);
        
        //得到最大的答案
        double ans = -1;
        for(int i=0; i<tree[root].size(); i++){
            ans = max(tree[root][i].L + tree[root][i].R, ans);
        } 
        printf("%.10lf\n", ans);
        
    }
    
} 

void dfs(int subset){
    if(vis[subset])
        return;
    vis[subset] = 1;
    
    bool hasChild = false;
    
    for(int left = ((subset-1) & subset); left > 0; left = ((left-1) & subset)){    //枚举集合 
        hasChild = true;
        
        int right = subset ^ left;        //left 的补集
        
        double l = sum[right] / sum[subset];    //左臂长度
        double r = 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 + l, tree[right][j].L - r);
                t.R = max(tree[right][j].R + r, tree[left][i].R - l);
                if(t.L + t.R < width){                    //如果满足条件,枚举结果加一 
                    tree[subset].push_back(t); 
                }
            }
        }
    }
    
    if(!hasChild){        //叶子结点的左右臂为零 
        tree[subset].push_back(Tree());
    }
    
}

 

转载于:https://www.cnblogs.com/lighter-blog/p/7216349.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值