HDU1100——Trees Made to Order以及卡特兰数

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;
}

代码思路

  1. Catalan 函数:目的是预先计算并存储卡特兰数。通过一个循环,根据卡特兰数的递推公式计算并填充 C 数组。

  2. PrintfAnswer 函数:

    • 这是核心的递归函数,用于根据给定的节点数 n 和排名 rank 来输出对应的二叉树结构。
    • 对于 n 为 1 的简单情况,直接输出 X 表示单个节点。
    • 当 rank 小于等于前 n - 1 个节点的卡特兰数 C[n - 1] 时,说明根节点没有左子树,先输出 X ,然后递归处理右子树。
    • 当 rank 大于等于从后数第 n - 1 个节点的卡特兰数偏移量时,说明根节点没有右子树,先递归处理左子树,再输出 X 。
    • 对于其他情况,通过循环找到左右子树节点数的组合,计算出新的排名,并分别递归处理左右子树。
  3. 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;
}

思路

  1. catalanNumber 函数:

    • 目的是计算卡特兰数。
    • 使用一个动态规划数组 dp 来存储中间结果。
    • 初始化 dp[0] 和 dp[1] 为 1 ,因为卡特兰数的初始情况。
    • 通过两层循环,对于每个 i (大于 1 ),计算 dp[i] 的值。它是通过累加 dp[j] * dp[i - j - 1] (其中 j 从 0 到 i - 1 )得到的,这是基于卡特兰数的递推关系。
  2. ticketChangeProblem 函数:

    • 这个函数直接调用 catalanNumber 函数来计算买票找零问题的结果,因为买票找零问题的方案数就是卡特兰数。
  3. main 函数:

    • 首先提示用户输入人数 n 。
    • 调用 ticketChangeProblem 函数计算方案数,并将结果存储在 ways 变量中。
    • 最后输出结果,即有多少种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零。
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值