uva1354

题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=41537

/*
solution:
    关键任务是枚举二叉树。
    自顶向下构造二叉树,每次枚举左子树用的哪个子集,则右子树就是使用剩下的子集。

note:
    关于枚举二叉树用到了二进制枚举法。A & B, A | B, A ^ B, 分别对应集合的交,并和对称差

date:
    2016/5/3
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>

using namespace std;
const int maxn = 8;

double r, w[maxn],  sum[1<<maxn];
int s, vis[1 << maxn];

struct Tree {
    double L, R;    //L是平衡点到最左边的距离,R类似
    Tree() : L(0), R(0) {}
};
vector<Tree> trees[1<<maxn];

void dfs(int subset) {
    if(vis[subset]) return;
    vis[subset] = 1;

    bool haveChild = false;
    for(int left = (subset - 1) & subset; left; left = (left - 1) & subset) {   //依次枚举左子树中的元素子集
        haveChild = true;

        int right = subset ^ left;
        double dl = sum[right] / sum[subset];
        double dr = sum[left] / sum[subset];

        dfs(left);  dfs(right);

        for(int i = 0; i < trees[left].size(); i++)
            for(int j = 0; j < trees[right].size(); j++) {
                Tree t;
                t.L = max(trees[left][i].L + dl, trees[right][j].L - dr);
                t.R = max(trees[right][j].R + dr, trees[left][i].R - dl);
                if(t.L + t.R < r)   trees[subset].push_back(t);
            }
    }
    if(!haveChild)  trees[subset].push_back(Tree());
}

int main() {
    freopen("input.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%lf%d", &r, &s);
        for(int i = 0; i < s; i++)  scanf("%lf", &w[i]);    //处理输入

        for(int i = 0; i < (1<<s); i++) {   //二进制法枚举子集,并将各个子树的重量分别求出来
            sum[i] = 0;
            trees[i].clear();

            for(int j = 0; j < s; j++)
                if(i & (1<<j))  sum[i] += w[j];
        }

        int root = (1 << s) - 1;
        memset(vis, 0, sizeof(vis));
        dfs(root);

        double ans = -1;
        for(int i = 0; i < trees[root].size(); i++) {
            ans = max(ans, trees[root][i].L + trees[root][i].R);
        }
        printf("%.10lf\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值