HDU1100——Trees Made to Order
题目描述
Problem - 1100
运行代码
#include <iostream>
#include <vector>
using namespace std;
vector<long long> C(21, 1); // 第21个卡特兰数达到65亿
// 预处理卡特兰数
void Catalan() {
for (int i = 1; i <= 20; i++) {
C[i] = C[i - 1] * (4 * i - 2) / (i + 1);
}
}
// 输出n个节点中,排序第rank的二叉树
void PrintfAnswer(int n, long long rank) {
if (n == 1) { // 只有1个节点
cout << "X";
return;
}
if (rank <= C[n - 1]) { // 排名靠前,即没有根节点没有左子树
cout << "X";
cout << "(";
PrintfAnswer(n - 1, rank);
cout << ")";
}
else if (rank >= C[n] - C[n - 1] + 1) { // 排名靠后,即没有根节点没有右子树
cout << "(";
PrintfAnswer(n - 1, rank - (C[n] - C[n - 1]));
cout << ")";
cout << "X";
}
else {
int t = n - 2;
long long temp;
long long ExceptLeftAllTree = C[n] - C[n - 1]; // 根节点除了只有左子树外,一共还有的树的种类(包括只有右子树)
int LeftTreeNum;
int RightTreeNum;
long long NewRank; // 找出的新的种类的树中的排序(求出左右子树数量的 该种类 中的排序)
for (int i = t; i >= n - 1 - t; i--) { // i为根节点左子树数量
temp = C[i] * C[n - 1 - i]; // 例如:若n = 5,则C[3]*C[1](即左子树为3,右子树为1的类型有temp种)
if (rank > ExceptLeftAllTree - temp) { // 检测是否符合左子树为i,又右子树为n-1-i
LeftTreeNum = i;
break;
}
else {
ExceptLeftAllTree -= temp;
}
}
// 求出左右子树各多少之后,算出在该类型树中的排序NewRank
NewRank = rank - (ExceptLeftAllTree - temp);
RightTreeNum = n - LeftTreeNum - 1;
cout << "(";
long long LeftTreeRank; // 左子树中的排序
// 这个地方需要注意:右子树的优先级是小于左子树的,所以每次判断子数中的排序时,注意左子树每变换一次,右子数变换C[RightTreeNum]次
if (NewRank < C[RightTreeNum]) { // 注意排序
LeftTreeRank = 1;
}
else if (NewRank % C[RightTreeNum] == 0) {
LeftTreeRank = NewRank / C[RightTreeNum];
}
else {
LeftTreeRank = NewRank / C[RightTreeNum] + 1;
}
PrintfAnswer(LeftTreeNum, LeftTreeRank);
cout << ")X(";
long long RightTreeRank; // 右子树中的排序
if (NewRank % C[RightTreeNum] == 0) {
RightTreeRank = C[RightTreeNum];
}
else {
RightTreeRank = NewRank % C[RightTreeNum];
}
PrintfAnswer(RightTreeNum, RightTreeRank);
cout << ")";
}
}
int main() {
Catalan(); // 预处理卡特兰数
long long n;
while (cin >> n && n) {
int i;
int c;
for (i = 1;; i++) {
if (n > C[i]) {
n -= C[i]; // 求出在当前节点所有排序中的名次
}
else {
c = i; // 求出当前是i节点组成的二叉树
break;
}
}
PrintfAnswer(c, n);
cout << endl;
}
return 0;
}
代码思路
-
Catalan
函数:目的是预先计算并存储卡特兰数。通过一个循环,根据卡特兰数的递推公式计算并填充C
数组。 -
PrintfAnswer
函数:- 这是核心的递归函数,用于根据给定的节点数
n
和排名rank
来输出对应的二叉树结构。 - 对于
n
为 1 的简单情况,直接输出X
表示单个节点。 - 当
rank
小于等于前n - 1
个节点的卡特兰数C[n - 1]
时,说明根节点没有左子树,先输出X
,然后递归处理右子树。 - 当
rank
大于等于从后数第n - 1
个节点的卡特兰数偏移量时,说明根节点没有右子树,先递归处理左子树,再输出X
。 - 对于其他情况,通过循环找到左右子树节点数的组合,计算出新的排名,并分别递归处理左右子树。
- 这是核心的递归函数,用于根据给定的节点数
-
main
函数:- 首先调用
Catalan
函数进行预处理。 - 然后在一个循环中,通过不断减去相应节点数的卡特兰数,找到输入的排名
n
所对应的节点数c
。 - 最后调用
PrintfAnswer
函数输出对应节点数和排名的二叉树结构。
- 首先调用
卡特兰数
卡特兰数(Catalan number)是组合数学中一个常出现于各种计数问题中的数列。
定义与通项公式:
递推关系:
特点和性质:
应用场景
括号化问题:
有n对括号,求括号正确配对的字符串数,例如 0 对括号:[空序列] 1 种可能;1 对括号:() 1 种可能;2 对括号:()() (()) 2 种可能 等 。
出栈次序问题:
一个栈(无穷大)的进栈序列为1,2,3...,有多少个不同的出栈序列。
凸多边形三角划分:
在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形,求不同划分的方案数。
路径问题:
- 在n×n的网格上,每次只能向右或向上走一格,在不穿越网格主对角线(从左下角到右上角的对角线)的情况下,从左下角走到右上角的不同路径计数。
- 一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班,如果她从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路 。
二叉树相关:
给定n个节点,能构成多少种不同的二叉树 。
#include <iostream>
// 计算卡特兰数
long long catalanNumber(int n) {
long long catalan[n + 1];
catalan[0] = catalan[1] = 1;
for (int i = 2; i <= n; i++) {
catalan[i] = 0;
for (int j = 0; j < i; j++) {
catalan[i] += catalan[j] * catalan[i - j - 1];
}
}
return catalan[n];
}
int main() {
int n;
std::cout << "请输入节点数量 n: ";
std::cin >> n;
long long numTrees = catalanNumber(n);
std::cout << "给定 " << n << " 个节点,可以构成 " << numTrees << " 种不同的二叉树。" << std::endl;
return 0;
}
买票找零问题:
有2n个人排成一行进入剧场。入场费 5 元。其中只有n个人有一张 5 元钞票,另外n人只有 10 元钞票,剧院无其它钞票,问有多少种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零(将持 5 元者到达视作将 5 元入栈,持 10 元者到达视作使栈中某 5 元出栈)。
#include <iostream>
// 计算卡特兰数的函数
long long catalanNumber(int n) {
long long dp[n + 1];
dp[0] = dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = 0;
for (int j = 0; j < i; j++) {
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
// 解决买票找零问题的函数
long long ticketChangeProblem(int n) {
return catalanNumber(n);
}
int main() {
int n;
std::cout << "请输入人数(n): ";
std::cin >> n;
long long ways = ticketChangeProblem(n);
std::cout << "有 " << ways << " 种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零。" << std::endl;
return 0;
}
思路
-
catalanNumber
函数:- 目的是计算卡特兰数。
- 使用一个动态规划数组
dp
来存储中间结果。 - 初始化
dp[0]
和dp[1]
为 1 ,因为卡特兰数的初始情况。 - 通过两层循环,对于每个
i
(大于 1 ),计算dp[i]
的值。它是通过累加dp[j] * dp[i - j - 1]
(其中j
从 0 到i - 1
)得到的,这是基于卡特兰数的递推关系。
-
ticketChangeProblem
函数:- 这个函数直接调用
catalanNumber
函数来计算买票找零问题的结果,因为买票找零问题的方案数就是卡特兰数。
- 这个函数直接调用
-
main
函数:- 首先提示用户输入人数
n
。 - 调用
ticketChangeProblem
函数计算方案数,并将结果存储在ways
变量中。 - 最后输出结果,即有多少种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零。
- 首先提示用户输入人数