Gym-102014I丨DP丨平衡树丨Hidden Tree(389ms/1168kB)

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;
}
/*
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值