BZOJ3057: 圣主的考验

Description

若对于二叉树T的每个节点v,其左子树的高度L和右子树的高度R均满足|L – R|≤1,则这个树T有可能来自超自然之界。规定若某节点子树为空,则该子树的高度是0。你的任务是求有N个节点的可能来自超自然之界的树的数目。

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3057

题解:
搬运题解,实在是神优化。。。

 

裸的树形DP很简单,F[i,j]表示i个节点,高度为j的方案数。枚举左子树有多少个节点转移即可。

 

但是这样是O(N^3)的,会超时。

 

注意到满足题目所述要求的树高度的范围很小。不妨把具有i个节点的满足题目要求的树的高度范围记为L[i]~R[i]。

 

那么我们先考虑如下两个问题:

 

高度为h的树最多有多少个节点。显然就是这个高度的满二叉树的节点个数。

 

即:max[h]=max[h-1]*2+1;

 

高度为h的树最少有多少个节点。画出前几项之后可以发现就是把h-1和h-2两个树分别作为h的左右子树。

 

即:min[h]=min[h-1]+min[h-2]+1;

 

求出max、min这两个数组之后,转化一下,扫描一遍即可求出L、R。

 

由于L[i]~R[i]的范围非常小,那么我们预处理3000以内的答案,然后O(1)回答即可。

 

applepi特意安排了一个trick——输出问题。答案不足九位,要直接输出。多于九位,要补足前导零。这个可以在本地打表找到分界点,大概是37~38这里是分界点。。。

代码:直接copy了。。。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <cstdlib>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 typedef long long ll;
 8 const int N = 3000, mod = 1000000000;
 9 int maxh[31], minh[31], L[N + 10], R[N + 10];
10 ll dp[N + 10][31], ans[N + 10]; 
11 
12 int main ()
13 {
14  freopen("domine.in", "r", stdin), freopen("domine.out", "w", stdout);
15  dp[0][0] = dp[1][1] = 1, ans[1] = 1;
16  minh[1] = 1, minh[2] = 2;
17  for (int i = 3; i <= 30; i++) minh[i] = minh[i - 1] + minh[i - 2] + 1;
18  for (int i = 1; minh[i] <= N && i <= 30; i++)
19   for (int j = minh[i]; j < min(minh[i + 1], N + 1); j++) R[j] = i;
20  maxh[1] = 1;
21  for (int i = 2; i <= 30; i++) maxh[i] = maxh[i - 1] * 2 + 1;
22  L[1] = 1;
23  for (int i = 2; maxh[i - 1] <= N && i <= 30; i++)
24   for (int j = maxh[i - 1] + 1; j <= min(maxh[i], N); j++) L[j] = i;
25  for (int i = 2; i <= N; i++)
26  {
27   for (int l = 0; l < i; l++)
28   {
29    int r = i - 1 - l;
30    for (int h = max(0, L[i] - 2); h <= min(29, R[i] - 1); h++)
31    {
32     if (h) dp[i][h + 1] += dp[l][h] * dp[r][h - 1];
33     (dp[i][h + 1] += dp[l][h] * dp[r][h]) %= mod;
34     dp[i][h + 2] += dp[l][h] * dp[r][h + 1];
35    }
36   }
37   for (int h = 1; h <= min(29, i); h++)
38    ans[i] += dp[i][h];
39   ans[i] %= mod;
40  }
41  int n; while (scanf("%d", &n), n)
42   printf(n >= 38 ? "%09lld\n" : "%lld\n", ans[n]);
43  fclose(stdin), fclose(stdout);
44  return 0;
45 }
View Code

 

 

posted @ 2014-10-29 16:38  ZYF-ZYF  Views(695)  Comments(7Edit  收藏  举报