http://codeforces.com/gym/102014/attachments
http://fastvj.rainng.com/solution/17446429
(还算比较快的吧~)
题意
定义一种平衡二叉树,要求每个非叶子节点两端子树叶子的权值相等。输入一组数,求这组数的子序列所能构成的最大的(这里指叶子最多的)这种树。
隐性要求权值到数组是先序遍历的顺序。
思路
第一遍观察应该要发现,因为是平衡树,父亲两端的权值相等,那就从下到上都是两倍关系。所以叶子的权值是有共同点的,他们都可以表示为(x*2^y)也就是对于统一个x乘以某个2的幂。
那么对于输入的数组可以就按照上述的x分组,下面只要我们能对每组数算出最大能建多大的平衡树,取一个最大值就是答案。
对于同组数来说,x系数是公因子,可以忽视不影响大小关系,剩下就是研究一组2的幂能依次构成多大的树。
下面提供一种思路,可以对一组数求和,然后遍历当前组内每个数vi,逐次从大到小类似背包的办法插入树里。设dp[i]表示综合为i时,这棵树最多能包含几个叶子。状态转移可以这样理解,我现在有权值为vi的叶子,那么对于dp[i-vi]的树,总能找到一处可以插入叶子构成一棵新的树(至于怎么调整树的结构我们并不关心,但肯定存在一个地方可以插入),那么dp[i]=max(dp[i-vi]+1,dp[i])这样就更新了。
比如说当前没有叶子节点,vi=2,那么可以说dp[2]=max(dp[0]+1,dp[2])。
需要注意的是,只需要在i是vi的倍数的时候更新就够了,如果不是的话,就不满足上面所说的,肯定存在一个地方可以插入,比如说i-vi<vi,那么无论怎么凑,总会有一个父亲两边的权值都不相等。
本组的答案会出现在,权值综合是2的倍数的树之内(也就是说有可能当前组内的数不都用上)。最后再每一组的答案里求最大值即可。
代码
dp题代码都不太长,思考的过程比较棘手。这题的代码顺便练习了一下C++11的lambda表达式和vector的用法,有兴趣的人可以试试用更简单的数组来实现,可能空间上会开销大一些。
#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#include<algorithm>
#include<iostream>
#include<numeric>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
const int INF = 0x3F3F3F3F;
int main(){
int n;
while(cin>>n,n){
vint a(n);
for(int i=0;i<n;i++)
cin>>a[i];
int ans=0;
vint p[555];
for(int x,i=0;i<n;i++){
x=a[i];
while(~x&1)x>>=1;
p[x].emplace_back(a[i]/x);
}
auto calc=[](vint &v){//lambda expression
int sum = accumulate(v.begin(),v.end(),0);
vint dp(sum+1,-1);
dp[0]=0;
for(int x:v)
for(int i=sum/x*x;i>0;i-=x)
if(~dp[i-x])
dp[i]=max(dp[i],dp[i-x]+1);
//building a tree
int res=0;
for(int i=1;i<=sum;i<<=1)
res=max(res,dp[i]);
return res;
};
for(vint v:p)
if(!v.empty())
ans=max(ans,calc(v));
cout<<ans<<endl;
}
return 0;
}
/*
*/