前言
确实是一道从未遇见过的神奇搜索题。
题目
题目大意
在一个宽度为 r(0<r<10) r ( 0 < r < 10 ) ( r r 为浮点数)的房间中,你想利用杠杆原理挂起个物品,每根木棍的长度都是 1 1 ,木棍的两端可以挂一个物品,或者挂另外一根木棍,木棍没有重量,第物品的重量是 wi(1≤wi≤1000) w i ( 1 ≤ w i ≤ 1000 ) ( wi w i 为整数)。请问在房间允许的条件下,你的“天平”最宽可以是多宽?
分析
s s 最大为,很可爱的数据范围,但是却发现大暴力似乎也无从下手。假设我们知道了天平的结构,计算它的宽度是比较容易的,不难发现天平可以看成一个二叉树,所以我们需要把可能形成的二叉树搜索出来。
怎么构造一个二叉树?我们需要知道以
u
u
为根的子树中需要有哪些结点,直接二进制状态压缩即可,然后枚举的左子树,就知道了
u
u
的右子树,递归两个子树得到的左右子树可能的形态。或许可以用一次dfs
完成这个操作,再用一次枚举或者dfs
统计答案。
作为一个懒癌晚期的人,我决定用一次dfs
完成构造+计算。
对于一个点集
s
s
<script type="math/tex" id="MathJax-Element-665">s</script>,记录它能形成的每棵二叉树最左端到根结点的支点的距离和最右端到根结点的支点的距离(因为每个结点就是一个棍子),用一个vector
数组存下来。
根据线段树的思想,这个数组可以在深搜时递推出来。
最后从全集的每个情况中统计答案即可。
代码
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define PDD pair<double,double>
#define MAXN 6
#define MAXS (1<<MAXN)-1
int N;
double R;
vector<PDD> Tree[MAXS+5];
int W[MAXN+5],Sum[MAXS+5];
//Sum[s]表示物品集s的总重量
//判断s是不是S的子集,如果你有更好的方法请无视它
bool Sub(int s,int S){
while(s){
if((s&1)&&(!(S&1)))
return 0;
s>>=1,S>>=1;
}
return 1;
}
void dfs(int S){
if(Tree[S].size())
return;
bool flag=1;
int Max=(1<<N)-1;
for(int Sl=0;Sl<=Max;Sl++){
int Sr=S^Sl;
if(!Sub(Sl,S)||!Sl||!Sr)
continue;
//枚举S的子集
flag=0;
dfs(Sl),dfs(Sr);//构造S的左右子树
double l1,l2,Left=0,Right=0;
//l1是当前棍子左端到支点的距离,l2是右端到支点的距离
l1=1.0/(Sum[Sl]*1.0/Sum[Sr]+1.0),l2=1.0-l1;
for(int i=0;i<int(Tree[Sl].size());i++){
for(int j=0;j<int(Tree[Sr].size());j++){
PDD Lt=Tree[Sl][i],Rt=Tree[Sr][j];
double tmpL=max(Lt.first,Rt.first-1)+l1,tmpR=max(Rt.second,Lt.second-1)+l2;
//注意这两个的更新,因为右子树最左端可能比左子树最左端更靠左
if(tmpL+tmpR-R>0)//保证房间装得下
continue;
//我只是把所有情况枚举出来,其他的不管
Tree[S].push_back(make_pair(tmpL,tmpR));
}
}
}
//如果它是叶子结点,需要新建一个空结点
if(flag)
Tree[S].push_back(make_pair(0,0));
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%lf%d",&R,&N);
for(int i=1;i<=N;i++)
scanf("%d",&W[i]);
memset(Sum,0,sizeof Sum);
//预处理Sum
int Max=(1<<N)-1;//Max是全集
for(int i=0;i<=Max;i++)
for(int j=0;j<N;j++)
if(i&(1<<j))
Sum[i]+=W[j+1];
dfs(Max);
//算答案,dfs时已经保证了房间装得下
double Ans=-1;
for(int i=0;i<int(Tree[Max].size());i++)
Ans=max(Ans,Tree[Max][i].first+Tree[Max][i].second);
printf("%.15lf\n",Ans);
for(int i=0;i<=Max;i++)
Tree[i].clear();
}
}