UVa 10516 Another Counting Problem

题目描述

树是计算机科学中重要的数据结构。在我们使用的所有树中,二叉树可能是最流行的一种。如果二叉树中的每个非叶子节点都有非空的左子树和右子树,则该二叉树被称为严格二叉树

我们将深度为 ddd 的严格二叉树定义为一棵严格二叉树,它至少有一条从根到叶子的路径长度为 ddd,并且该树中没有从根到叶子的路径长度大于 ddd

现在我们将这个概念推广到 nnn 叉树:

  • 如果 nnn 叉树中的每个非叶子节点都有 nnn 个子节点,则该 nnn 叉树被称为严格 nnn 叉树
  • 深度为 ddd 的严格 nnn 叉树可以定义为一棵严格 nnn 叉树,它至少有一条从根到叶子的路径长度为 ddd,并且该树中没有从根到叶子的路径长度大于 ddd

给定 nnn 和深度 ddd,你的任务是找出深度为 ddd 的不同严格 nnn 叉树的数量。

输入格式

输入包含多个测试用例。每个测试用例包含两个整数:

  • nnn (0<n≤32)(0 < n \leq 32)(0<n32)
  • ddd (0≤d≤16)(0 \leq d \leq 16)(0d16)

当输入 n=0n = 0n=0d=0d = 0d=0 时,输入终止,不需要处理这个测试用例。

输出格式

对于每个测试用例,输出一行三个整数:nnnddd 和深度为 ddd 的不同严格 nnn 叉树的数量。

样例输入

2 0
2 1
2 2
2 3
3 5
0 0

样例输出

2 0 1
2 1 1
2 2 3
2 3 21
3 5 58871587162270592645034001

题目分析

问题理解

我们需要计算深度恰好ddd 的严格 nnn 叉树的数量。关键在于理解"深度恰好为 ddd"的含义:

  • 至少有一条从根到叶子的路径长度为 ddd
  • 所有从根到叶子的路径长度都不超过 ddd
  • 至少有一条路径长度等于 ddd(这排除了深度小于 ddd 的树)

关键思路

为了解决这个问题,我们采用递推的方法:

  1. 定义状态

    • G(d)G(d)G(d) 表示深度不超过 ddd 的严格 nnn 叉树的数量
    • F(d)F(d)F(d) 表示深度恰好ddd 的严格 nnn 叉树的数量
  2. 关系建立

    • 显然,F(d)=G(d)−G(d−1)F(d) = G(d) - G(d-1)F(d)=G(d)G(d1)
    • 深度不超过 ddd 的树包括深度为 0,1,2,…,d0, 1, 2, \ldots, d0,1,2,,d 的所有树
  3. 递推公式

    • 对于深度不超过 ddd 的严格 nnn 叉树:
      • 如果深度为 000,只有根节点,有 111 种情况
      • 如果深度大于 000,根节点有 nnn 个子树,每个子树的深度都不超过 d−1d-1d1
    • 因此,递推公式为:
      G(0)=1 G(0) = 1 G(0)=1
      G(d)=G(d−1)n+1for d≥1 G(d) = G(d-1)^n + 1 \quad \text{for } d \geq 1 G(d)=G(d1)n+1for d1
      这里的 +1+1+1 是考虑只有根节点的情况
  4. 最终计算

    • 深度恰好为 ddd 的树的数量:F(d)=G(d)−G(d−1)F(d) = G(d) - G(d-1)F(d)=G(d)G(d1)

验证递推公式

n=2n = 2n=2(二叉树)为例:

  • G(0)=1G(0) = 1G(0)=1(只有根节点)
  • G(1)=G(0)2+1=12+1=2G(1) = G(0)^2 + 1 = 1^2 + 1 = 2G(1)=G(0)2+1=12+1=2
  • G(2)=G(1)2+1=22+1=5G(2) = G(1)^2 + 1 = 2^2 + 1 = 5G(2)=G(1)2+1=22+1=5
  • G(3)=G(2)2+1=52+1=26G(3) = G(2)^2 + 1 = 5^2 + 1 = 26G(3)=G(2)2+1=52+1=26

计算深度恰好为 ddd 的树:

  • F(0)=G(0)=1F(0) = G(0) = 1F(0)=G(0)=1
  • F(1)=G(1)−G(0)=2−1=1F(1) = G(1) - G(0) = 2 - 1 = 1F(1)=G(1)G(0)=21=1
  • F(2)=G(2)−G(1)=5−2=3F(2) = G(2) - G(1) = 5 - 2 = 3F(2)=G(2)G(1)=52=3
  • F(3)=G(3)−G(2)=26−5=21F(3) = G(3) - G(2) = 26 - 5 = 21F(3)=G(3)G(2)=265=21

这与样例输出完全一致。

大数处理

由于答案可能非常大(题目提示可能达到 200200200 位整数),我们需要实现高精度整数运算来支持大数的加法、减法、乘法和幂运算。

算法实现

高精度整数类

我们实现一个 BigInt\texttt{BigInt}BigInt 类来处理大数运算,包括:

  1. 构造函数:从整数或字符串初始化
  2. 基本运算:加法、减法、乘法
  3. 幂运算:使用快速幂算法
  4. 辅助函数:去除前导零、转换为字符串

主要算法步骤

  1. 读取输入 nnnddd
  2. 如果 n=0n = 0n=0d=0d = 0d=0,结束程序
  3. 计算 GGG 数组:
    • G[0]=1G[0] = 1G[0]=1
    • 对于 iii111dddG[i]=G[i−1]n+1G[i] = G[i-1]^n + 1G[i]=G[i1]n+1
  4. 计算结果:
    • 如果 d=0d = 0d=0,结果为 G[0]G[0]G[0]
    • 否则,结果为 G[d]−G[d−1]G[d] - G[d-1]G[d]G[d1]
  5. 输出 nnnddd 和结果

复杂度分析

  • 时间复杂度O(d⋅n⋅L2)O(d \cdot n \cdot L^2)O(dnL2),其中 LLL 是大数的位数
  • 空间复杂度O(d⋅L)O(d \cdot L)O(dL),用于存储 GGG 数组

代码实现

// Another Counting Problem
// UVa ID: 10516
// Verdict: Accepted
// Submission Date: 2025-11-08
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

// 高精度整数类
class BigInt {
public:
    vector<int> digits;

    BigInt() {}
    
    BigInt(long long num) {
        if (num == 0) digits.push_back(0);
        while (num > 0) {
            digits.push_back(num % 10);
            num /= 10;
        }
    }
    
    BigInt(const string& str) {
        for (int i = str.size() - 1; i >= 0; i--) {
            digits.push_back(str[i] - '0');
        }
        removeLeadingZeros();
    }
    
    void removeLeadingZeros() {
        while (digits.size() > 1 && digits.back() == 0) {
            digits.pop_back();
        }
    }
    
    BigInt operator+(const BigInt& other) const {
        BigInt result;
        int carry = 0;
        int maxSize = max(digits.size(), other.digits.size());
        
        for (int i = 0; i < maxSize || carry; i++) {
            int sum = carry;
            if (i < digits.size()) sum += digits[i];
            if (i < other.digits.size()) sum += other.digits[i];
            result.digits.push_back(sum % 10);
            carry = sum / 10;
        }
        
        return result;
    }
    
    BigInt operator-(const BigInt& other) const {
        BigInt result;
        int borrow = 0;
        
        for (int i = 0; i < digits.size(); i++) {
            int diff = digits[i] - borrow;
            if (i < other.digits.size()) diff -= other.digits[i];
            if (diff < 0) {
                diff += 10;
                borrow = 1;
            } else {
                borrow = 0;
            }
            result.digits.push_back(diff);
        }
        
        result.removeLeadingZeros();
        return result;
    }
    
    BigInt operator*(const BigInt& other) const {
        BigInt result;
        result.digits.resize(digits.size() + other.digits.size(), 0);
        
        for (int i = 0; i < digits.size(); i++) {
            int carry = 0;
            for (int j = 0; j < other.digits.size() || carry; j++) {
                long long product = result.digits[i + j] + carry;
                if (j < other.digits.size()) product += (long long)digits[i] * other.digits[j];
                result.digits[i + j] = product % 10;
                carry = product / 10;
            }
        }
        
        result.removeLeadingZeros();
        return result;
    }
    
    BigInt pow(int exponent) const {
        BigInt result(1);
        BigInt base = *this;
        
        while (exponent > 0) {
            if (exponent & 1) {
                result = result * base;
            }
            base = base * base;
            exponent >>= 1;
        }
        
        return result;
    }
    
    string toString() const {
        string str;
        for (int i = digits.size() - 1; i >= 0; i--) {
            str += to_string(digits[i]);
        }
        return str;
    }
};

int main() {
    int n, d;
    
    while (cin >> n >> d) {
        if (n == 0 && d == 0) {
            break;
        }
        
        vector<BigInt> g(d + 2); // G[d] = 深度不超过d的树的数量
        g[0] = BigInt(1); // 深度0:只有根节点
        
        // 计算G[d]
        for (int i = 1; i <= d; i++) {
            g[i] = g[i - 1].pow(n) + BigInt(1);
        }
        
        // 计算F[d] = G[d] - G[d-1]
        BigInt result;
        if (d == 0) {
            result = g[0];
        } else {
            result = g[d] - g[d - 1];
        }
        
        cout << n << " " << d << " " << result.toString() << endl;
    }
    
    return 0;
}

总结

本题的关键在于理解严格 nnn 叉树的定义,并通过递推关系将问题转化为计算深度不超过 ddd 的树的数量。通过定义 G(d)G(d)G(d)F(d)F(d)F(d) 的关系,我们能够高效地计算出结果。

由于答案可能非常大,我们需要实现高精度运算来处理大数。算法的时间复杂度主要取决于大数运算的效率,但对于题目给定的数据范围(n≤32n \leq 32n32, d≤16d \leq 16d16),这个解法是完全可行的。

这种递推思想在组合数学和树计数问题中非常常见,掌握这种方法对于解决类似的计数问题很有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值