【JZOJ3601】【广州市选2014】Tree(tree)

╰( ̄▽ ̄)╭

每个非叶子节点,其左右子树叶子节点的权值之和相等。我们称这种二叉树叫平衡二叉树。

我们将一棵平衡二叉树叶子节点的权值从左到右列出来,假如这个权值序列是另一个序列A的子序列,我们称这棵平衡二叉树“隐藏”在序列A当中。在本题中,我们称一个序列S2是另一个序列S1的子序列,当且仅当S2可以由S1中删除0个或多个元素,但不改变S1中剩余元素的相对位置获得。

你的任务是对给定的整数序列,寻找当中隐藏的具有最多叶子节点的平衡二叉树。

n<=10001<=ai<=500

(⊙ ▽ ⊙)

显而易见,我们先枚举一个 base ,并将所有满足 a[i]=base2j 的提取出来,
形成一个新的数列 A
那么原问题就转化为:对于一个只有2的幂数的数列A,求一个最多叶子结点的隐藏平衡二叉树。


容易想到,可以利用动态规划来做。
但问题在于如何写转移方程。


如果我们摒弃时间复杂度不谈,
f[i][j] 表示前 i 个数中,未合并的数之和为j,的最多合并次数。
显然 f[i1][j]+1f[i][j+A[i]] (A[i]<=lowbit(j))

先明白 lowbit() 的意义。
lowbit(x) 表示 x 的二进制中,只保留最低位的1及其后面的 0 ,得到的数。

由于A[i]<=lowbit(j),理解为, A[i] 可以暂时储存在 j 中,因此可以转移。
如果不满足A[i]<=lowbit(j),会导致不连续的合并,是不允许的。


f[i][j] 的第一维可以滚动;
第二维,可以只枚举可以达到的和的最大值。

这样优化之后,可以勉强卡过。

( ̄~ ̄)

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define ll long long
using namespace std;
const char* fin="tree.in";
const char* fout="tree.out";
const int inf=0x7fffffff;
const int maxn=1007,maxa=507,maxk=300000;
int n,i,j,k,ans=1;
int a[maxn];
int b[maxn],mi[maxn];
int f[maxk];
bool bz[maxa];
void solve(){
    int i,j,k,l,MAX=0;
    f[0]=0;
    for (i=1;i<=b[0];i++){
        for (j=MAX;j>=0;j--){
            if (j==0 || (j&-j)>=b[i]){
                k=j+b[i];
                if (k>=maxk) continue;
                f[k]=max(f[k],f[j]+1);
                if ((k&-k)==k) ans=max(ans,f[k]);
                MAX=max(MAX,k);
            }
        }
    }
}
int main(){
    freopen(fin,"r",stdin);
    freopen(fout,"w",stdout);
    scanf("%d",&n);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    //for (i=1,j=0;i<1<<maxk;i<<=1,j++) po[i]=j;
    for (i=1;i<maxa;i++){
        memset(bz,0,sizeof(bz));
        memset(mi,0,sizeof(mi));
        memset(f,128,sizeof(f));
        for (j=i,k=0;j<maxa;j=j*2){
            bz[j]=true;
            mi[j]=++k;
        }
        b[0]=0;
        for (j=1;j<=n;j++)
            if (bz[a[j]]) b[++b[0]]=a[j]/i;
        if (b[0]) solve();
    }
    printf("%d",ans);
    return 0;
}

(⊙v⊙)

关键点:
1.把原数列中提取出一个新的数列。
通过枚举,来简化问题。
2.运用特殊的DP技巧
本题的具体操作是,发现了题目中的特殊性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值