题目链接: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;
}