usaco2.3 nocows dp + 失败的递归

来源:互联网 发布:淘宝卖家论坛 编辑:程序博客网 时间:2024/05/17 07:47

题目链接


题目大意:

给出树的节点个数和深度,并且对树的限定是,每个节点的孩子数为 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: nocowsLANG: 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: nocowsLANG: 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 太差劲...。

0 0
原创粉丝点击