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 }