题目大意:给出房间宽度,重物个数n(n<=6)以及重物数量;每个天平的长度为一,每个天平的左右可以放重物或者另一个天平,要求在不超过房间宽度的情况下,使得天平尽量宽。
思路:显然的搜索题。由于重物的个数最多只有6个,因此我们可以状态压缩,用一个01串表示。然后暴力枚举左右天平的情况,用记忆化搜索。注意左臂为负数,右臂为正数
#include<cstdio>
#include<vector>
#include<cstring>
#define MAXN 1<<6
#define Max(a,b) a>b?a:b
#define Min(a,b) a<b?a:b
using namespace std;
struct T
{
double llen;
double rlen;
T(){}
T(double _llen,double _rlen)
{
llen = _llen;
rlen = _rlen;
}
};
vector<T> a[MAXN];
int n;
double limit;
double w[6],sum[MAXN];
bool vis[1<<6];
int bitcount(int x)
{
int count = 0;
while(x)
{
if(x&1) count++;
x >>= 1;
}
return count;
}
void dfs(int s)
{
if(vis[s]) return;//已经枚举过
vis[s] = 1;
if(bitcount(s) == 1)//叶子节点
{
a[s].push_back(T(0,0));
return;
}
for(int sl = (s - 1)&s; sl ; sl = (sl - 1)&s)//枚举左子树
{
int sr = s^sl;//右子树
dfs(sl);
dfs(sr);
for(int i = 0; i < a[sl].size(); i++)
{
for(int j = 0; j < a[sr].size(); j++)
{
double tl = sum[sr]/(sum[sr] + sum[sl]);
double tr = sum[sl]/(sum[sr] + sum[sl]);
double ll = Min(-sum[sr]/(sum[sr] + sum[sl]) + a[sl][i].llen,sum[sl]/(sum[sr] + sum[sl]) + a[sr][j].llen);//比较 左臂+左子天平的左臂 与 右子天平的左臂-右臂 谁更小(左臂为负)
double rr = Max(sum[sl]/(sum[sr] + sum[sl]) + a[sr][j].rlen,-sum[sr]/(sum[sr] + sum[sl]) + a[sl][i].rlen);//比较 右臂+右子天平的右臂 与 左子天平的右臂-左臂 谁更大(右臂为正)
a[s].push_back(T(ll,rr));
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(vis,0,sizeof(vis));
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
scanf("%lf%d",&limit,&n);
for(int i = 0; i < n; i++)
scanf("%lf",&w[i]);
int len = 1<<n;
for(int s = 0; s < len; s++)
{
for(int i = 0; i < n; i++)
{
if(s&(1<<i))
sum[s] += w[i];
}
}
double ans = -1;
int s = (1<<n) - 1;
dfs(s);
for(int i = 0; i < a[s].size(); i++)// 预处理每种情况的重量
{
if(a[s][i].rlen - a[s][i].llen < limit)
{
if(a[s][i].rlen - a[s][i].llen > ans)
{
ans = a[s][i].rlen - a[s][i].llen;
}
}
}
if(ans == -1) printf("-1\n");
else printf("%.16lf\n",ans);
}
}