E. 学姐的数列 2014新生暑假个人排位赛03
题目描述
定义一颗平衡二叉树满足条件:对于任意节点,其左子树的权值之和等于右子树权值之和。如图:
我们用序列表示上图平衡二叉树,即4 1 1 2 4 4。
现在给定一个序列,求其能最长的能构成平衡二叉树的子序列。子序列元素为原序列的子集,且元素间保持原顺序。
输入格式
输入第一行为数据组数 T(T≤10) ,接下来 T 组数据,每组第一行 n(1≤n≤128) 为序列元素个数,下一行给出 n 个正整数, a i (a i ≤7) 表示第 i 个数为2^ a i 。
输出格式
每组数据输出一行,符合题意的最长子序列的长度。
输入样例
2
6
2 0 0 1 2 2
5
0 0 0 0 0
输出样例
6
4
思路:一开始先想搜索,但是由于子序列不是连着的,搜索的话,一个要枚举区间。还有枚举区间内取的每一项和项数,不太可能了。然后想到DP记录下搜索得到的某些性质,然后就想到区间上构成的数的最大长度,但是我们左右子树的和要相等,故要把和记录下来。但记录和的话为O(n^4)会超时,且发现每个树的合法的和为2^i次方,只需要枚举i即可,则时间可以下降到O(n^3).可以出解。设dp[i][j][w]为区间[i,j]上和为2^w的最大长度,不难得出,dp[i][j][w]=max(dp[i][k][w-1]+dp[k+1][j][w-1])(i<=k<j),初始化为dp[i][j][a[t]]=1,(i<=t<=j)。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#define maxn 200
#define LOCAL
using namespace std;
int n;
int a[maxn],dp[maxn][maxn][50];
int main()
{
#ifdef LOCAL
freopen("input.txt","r",stdin);
#endif // LOCAL
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
for(int k=i;k<=j;k++)dp[i][j][a[k]]=1;
for(int w=1;w<20;w++)
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
for(int k=i;k<j;k++)
{
if(dp[i][k][w-1]==0||dp[k+1][j][w-1]==0)continue;
dp[i][j][w]=max(dp[i][k][w-1]+dp[k+1][j][w-1],dp[i][j][w]);
}
int ans=-1;
for(int w=0;w<20;w++)
for(int i=1;i<n;i++)
for(int j=i;j<=n;j++)ans=max(ans,dp[i][j][w]);
printf("%d\n",ans);
}
return 0;
}