题目大意:
给出树的节点个数和深度,并且对树的限定是,每个节点的孩子数为 2 或为 0,问有多少种形态不同的数。
思路:
最开始的时候是这样想找规律的
举个例子吧,比如节点数 11, 深度 5
很显然前两层都是满的,剩下节点 11 - 3 = 8,这 8 个要分到 3 层去.
可以是 2 2 4, 2 4 2, 4 2 2.
要求是后一个数 <= 前一个数 * 2 (最前面两个数固定的是 1 2)
因为下一层的两个节点是作为一个整体接到上一层的一个节点上的,上一层有 m 个节点,这一层有 n 个节点,那么就有 C m (n/2)
上面那个例子就是 C21 * C21 * C22 + C21 * C22 * C41 + C22 * C41 * C21 = 20.
是的方法就是这样的,可是并没有什么用,毕竟测试数据里层数多达 100 层,这样递归的找符合情况的组数显然会爆。
于是后来就换了个想法,dp 啊,我们要计算 K 层 N 个节点,只要考虑它的 左子树 和 右子数,然后再递归不就可以了吗
(我严重怀疑当时是怎么想的,显然是递归就也会爆啊。)
借着这个思路,既然是左右等价,那么只需要找到
左子树节点数 >= 右子树的深度,并且左子树深度 = h - 1,右子数深度 <= h - 1,
满足这个条件的,对于所有左子树可能的节点数,对 左子树深度 = h - 1 的情况总数 * 右子数所有可能的深度的情况总数 * 2 求个和,不就好了吗
(其中对于 左子树节点个数 == 右子树节点个数 且 左子树深度 == 右子树深度的,不需要 *2)
是的,感觉完全没问题。
然而就是有错。
为什么呢?
我苦思冥想了很久,知道了问题出在哪里。
满足 左子树节点数 >= 右子树节点数 时,我条件反射地只算了 左子树深度 = h - 1 的情况,事实上如果右子树深度可以达到 h - 1,那么左子树深度 <= h - 1 也是可以的
想通这点的时候,真的是拨开云雾见青天啊(误
事实上显然还是爆了。
但是对于深度 <= 32 的,正确性还是能保证的,就放一下代码吧(毕竟我辛辛苦苦((。
/*
PROB: nocows
LANG: C++
ID: fan_0111
*/
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
int rec[210][110];
int f(int n, int h) {
if (rec[n][h]) return rec[n][h];
int Min = 2 * (h-1) - 1, tot = n-1, ans = 0;
int le = Min, ri = tot - le;
while (le < ri) { le += 2; ri -= 2; }
for (; le <= (2 << (h-2)) -1 && tot - le >= 1; le += 2) {
int ri = tot - le;
int loR = ceil(log(ri+1)/log(2)), hiR = min((ri+1) / 2, h-1);
for (int j = loR; j <= hiR; ++j) {
int x = ((LL)f(le, h-1) * f(ri, j)) % 9901;
if (le == ri && j == h-1) ans = (ans + (LL)x) % 9901;
else ans = (ans + 2*(LL)x) % 9901;
}
if (le != ri && hiR == h-1) {
int loL = ceil(log(le+1)/log(2)), hiL = h-1;
for (int i = loL; i < hiL; ++i) {
int x = ((LL)f(le, i) * f(ri, hiR)) % 9901;
ans = (ans + 2*(LL)x) % 9901;
}
}
}
rec[n][h] = ans;
return ans;
}
int main() {
// freopen("nocows.in", "r", stdin);
// freopen("nocows.out", "w", stdout);
int n, k;
rec[1][1] = 1; rec[3][2] = 1;
cin >> n >> k;
if (!(n & 1)) cout << 0 << endl;
// for (int k = 1; k <= 65; ++k) {
// for (int n = 2*k-1; n <= (1 << k)-1; ++n) cout << f(n, k) << " ";
// cout << endl;
// }
else cout << f(n, k) << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
后来......黔驴技穷去百度了。
看到一个十分简单的 dp (叹
其实一开始并不需要考虑深度是不是这么多,只需要用深度为 h 的减去深度为 h-1,得到的情况就都只剩下深度为 h 的了。
这样写起来也十分的方便简洁。
AC代码如下:
/*
PROB: nocows
LANG: C++
ID: fan_0111
*/
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
int ans[110][210];
int main() {
freopen("nocows.in", "r", stdin);
freopen("nocows.out", "w", stdout);
int n, k;
cin >> n >> k;
for (int i = 1; i <= k; ++i) ans[i][1] = 1;
for (int i = 2; i <= k; ++i) {
for (int j = 3; j <= n; j += 2) {
for (int k = 1; k <= j-2; k += 2) {
ans[i][j] = (ans[i][j] + ((LL)ans[i-1][k] * ans[i-1][j-1-k]) % 9901) % 9901;
}
}
}
cout << (ans[k][n] - ans[k-1][n]+9901) % 9901 << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
要反思。
一开始总是往复杂的方向去想了,平白浪费了很多时间。
没有好好想想可行性就开始写,也是浪费。
还有对情况的考虑不周全,自以为没问题。
还有最根本的 dp 太差劲...。