题目:
求N个结点能够组成的二叉树的个数(2<=N<=1000)。
例:
输入:
3
输出:
5
分析:看别人的博文说是卡特兰数;我首先想到的是找规律,用递推公式求解,因为我不知道卡特兰数; 寻找出规律之后 当然这里结果可能会超过long long的表示范围,要处理大数问题;
先分析规律,假设n个结点能够组成的二叉树数量为f(n),根节点是固定的,假设左子树有i(0<=i<=n-1)个结点,则右子树有n-1-i个结点,此时可能组成的二叉树数量为f(i) * f(n-1-i);所以有:
f(n) = f(0) * f(n-1) + f(1) * f(n-1-1) + … + f(n-1) * f(n-1-(n-1))
= f(0) * f(n-1) + f(1) * f(n-2) + … + f(n-1) * f(0)
但是计算一下复杂度,用上面的式子求f(n)的复杂度为O(n2),处理大数时时间复杂度为O(n),程序总的时间复杂度为O(n3);这个时间复杂度肯定是要超时了;此时我无奈的发现,不用卡特兰数是没办法解题了;
上面的式子进一步推导,得到递推公式(当然请原谅我并没有去推导,我觉得自己在考场上也不可能推导出来):
f(n)= f(n-1)(4n-2)/(n+1)
我们不妨再看一看卡特兰数的通项公式:
f(n)=C(n, 2n)/(n+1) = (2n)!/((n!)(n+1)!)
关于卡特兰问题的讲解我在知乎上看到一篇很不错,可以去看看,
知乎-卡特兰数.
但是!递推公式里面里面有除法,通项公式里面也有除法!难道我不会JAVA的话,要在考场上手撸一遍大数的加法,乘法,还有除法嘛?!
算了算了,我还是抛弃卡特兰数吧,毕竟我不太会写大数除法,代码量太大了;2<=n<=100这个范围内,还是可以正确的,想拿满分,等我以后进一步学习吧。
#include<iostream>
using namespace std;
struct bigInteger {
int digit[1010], size;
bigInteger() {
memset(digit, 0, sizeof(digit));
size = 0;
}
void set(string str) {
int L = str.length();
for (int i = L - 1; i >= 0; i--)
digit[size++] = str[i]-'0';
}
void output() {
for (int i = size - 1; i >= 0; i--)
printf("%d", digit[i]);
}
bigInteger operator +(const bigInteger& A){
bigInteger ret;
int carry = 0;
for (int i = 0; i < A.size || i < size; i++){
int tmp = A.digit[i] + digit[i] + carry;
carry = tmp / 10;
tmp %= 10;
ret.digit[ret.size++] = tmp;
}
if (carry != 0)
ret.digit[ret.size++] = carry;
return ret;
}
bigInteger operator *(const bigInteger& A){
bigInteger ret;
ret.size = A.size + size;
for (int i = 0; i < A.size; i++) {
for (int j = 0; j < size; j++)
ret.digit[i + j] += A.digit[i]*digit[j];
}
int carry = 0;
for (int i = 0; i < ret.size; i++) {
ret.digit[i] += carry;
carry = ret.digit[i] / 10;
ret.digit[i] %= 10;
}
while (ret.digit[ret.size - 1] == 0 && ret.size > 1)
ret.size--;
return ret;
}
};
bigInteger dp[1010];
int main() {
int n;
scanf("%d", &n);
dp[0].set("1");
dp[1].set("1");
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= i - 1; j++)
dp[i] = dp[i] + dp[j] * dp[i - 1 - j];
}
dp[n].output();
return 0;
}