7-7天平难题(Mobile Computing,uva 1354)
(mobile.cpp,Time Limit:3000MS )
题目描述:
给出房间的宽度r和s个挂坠的重量wi,设计一个尽量宽(但宽度不能超过房间宽度r)的天平,挂着所有挂坠。
天平由一些长度为1的木棍组成。木棍的每一端要么挂一个挂坠,要么挂另外一个木棍。如图1所示,设n和m分别是两端的总重量,要让天平平衡,必须满足n*a=m*b。
例如:如果有3个重量分别为1,1,2的挂坠,有3种平衡的天平,如下图所示:
挂坠的宽度忽略不计,且不同的子天平可以相互重叠。如下图所示,宽度为(1/3)+1+(1/4)。
输入格式:
输入第一行为数据组数。每组数据前两行为房间宽度r和挂坠数目s (0 < r < 10,1<=s<=6)。以下s行为一个挂坠的重量wi(1<=wi<=1000)。输入保证不存在天平的宽度恰好在r-10^(-5)和r+10^(-5)(这样可以保证不会出现精度问题)。
输出格式:
对于每组数据,输出最优天平的宽度。如果无解,输出-1。你的输出和标准答案的绝对误差不应超过10^(-8)。
样例输入:
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
样例输出:
-1
1.3333333333333335
1.6666666666666667
1.5833333333333335
1.7142857142857142
题目分析:(搜索)
天平的形态最后可以看成二叉树,所以问题转化成了我们有一堆点(点数还不超过6),要把这一堆点合成一颗树。我们可以用哈弗曼树的想法,每次把两个节点合成一个新的节点,最终只剩下一个节点,就是一组可行解,更新答案。
但是我们不知道每次要合并哪两个点,因为层数很少,所以可以暴力枚举。
这样这道题就做完了,我的做法灰常辣鸡,没有用什么高端的剪枝和搜索技巧。
想知道更多搜索基础知识和优化技巧,可以到其他神犇的博客学习。
注意事项:
在合并两个点的时候,有可能出现这样的情况:
红色是一个节点,蓝色是一个节点。
把这两个点合到一起的时候,右边的那只明显越界了啊,把左边那只给包起来了,所以我们可以发现,这个节点的左边界不仅仅跟左边的节点有关系,还跟右边的节点有关系,这种情况需要在合并的时候判断出来。
代码如下:(代码写的很辣鸡,勿喷)
#include<cstdio>
#include<iostream>
using namespace std;
struct balance{
double w,ls,rs;
balance operator + (const balance c)
{
balance z;
z.w=w+c.w;
double l=c.w/(w+c.w);
double r=w/(w+c.w);
z.ls=max(l+ls,c.ls-r);
z.rs=max(r+c.rs,rs-l);
return z;
}
}a[10];
double r,ans;
int s,T;
void dfs(int c)
{
if(c==s)
{
double cs=a[1].ls+a[1].rs;
if(cs<=r && cs>ans) ans=cs;
return;
}
balance b[10],d[10];
for(int i=1;i<=s-c+1;i++) b[i]=a[i];
for(int i=1;i<=s-c+1;i++)
for(int j=1;j<=s-c+1;j++)
{
if(i==j) continue;
for(int k=1;k<=s-c+1;k++) a[k]=b[k];
int top=0;
for(int k=1;k<=s-c+1;k++)
if(k!=i && k!=j) d[++top]=a[k];
d[++top]=a[i]+a[j];
for(int k=1;k<=top;k++) a[k]=d[k];
dfs(c+1);
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lf%d",&r,&s);
for(int i=1;i<=s;i++)
{
scanf("%lf",&a[i].w);
a[i].ls=0;
a[i].rs=0;
}
ans=-1;
dfs(1);
printf("%.10lf\n",ans);
}
return 0;
}