题目描述
树是计算机科学中重要的数据结构。在我们使用的所有树中,二叉树可能是最流行的一种。如果二叉树中的每个非叶子节点都有非空的左子树和右子树,则该二叉树被称为严格二叉树。
我们将深度为 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<n≤32)
- ddd (0≤d≤16)(0 \leq d \leq 16)(0≤d≤16)
当输入 n=0n = 0n=0 且 d=0d = 0d=0 时,输入终止,不需要处理这个测试用例。
输出格式
对于每个测试用例,输出一行三个整数:nnn、ddd 和深度为 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 的树)
关键思路
为了解决这个问题,我们采用递推的方法:
-
定义状态:
- 设 G(d)G(d)G(d) 表示深度不超过 ddd 的严格 nnn 叉树的数量
- 设 F(d)F(d)F(d) 表示深度恰好为 ddd 的严格 nnn 叉树的数量
-
关系建立:
- 显然,F(d)=G(d)−G(d−1)F(d) = G(d) - G(d-1)F(d)=G(d)−G(d−1)
- 深度不超过 ddd 的树包括深度为 0,1,2,…,d0, 1, 2, \ldots, d0,1,2,…,d 的所有树
-
递推公式:
- 对于深度不超过 ddd 的严格 nnn 叉树:
- 如果深度为 000,只有根节点,有 111 种情况
- 如果深度大于 000,根节点有 nnn 个子树,每个子树的深度都不超过 d−1d-1d−1
- 因此,递推公式为:
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(d−1)n+1for d≥1
这里的 +1+1+1 是考虑只有根节点的情况
- 对于深度不超过 ddd 的严格 nnn 叉树:
-
最终计算:
- 深度恰好为 ddd 的树的数量:F(d)=G(d)−G(d−1)F(d) = G(d) - G(d-1)F(d)=G(d)−G(d−1)
验证递推公式
以 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)=2−1=1
- F(2)=G(2)−G(1)=5−2=3F(2) = G(2) - G(1) = 5 - 2 = 3F(2)=G(2)−G(1)=5−2=3
- F(3)=G(3)−G(2)=26−5=21F(3) = G(3) - G(2) = 26 - 5 = 21F(3)=G(3)−G(2)=26−5=21
这与样例输出完全一致。
大数处理
由于答案可能非常大(题目提示可能达到 200200200 位整数),我们需要实现高精度整数运算来支持大数的加法、减法、乘法和幂运算。
算法实现
高精度整数类
我们实现一个 BigInt\texttt{BigInt}BigInt 类来处理大数运算,包括:
- 构造函数:从整数或字符串初始化
- 基本运算:加法、减法、乘法
- 幂运算:使用快速幂算法
- 辅助函数:去除前导零、转换为字符串
主要算法步骤
- 读取输入 nnn 和 ddd
- 如果 n=0n = 0n=0 且 d=0d = 0d=0,结束程序
- 计算 GGG 数组:
- G[0]=1G[0] = 1G[0]=1
- 对于 iii 从 111 到 ddd:G[i]=G[i−1]n+1G[i] = G[i-1]^n + 1G[i]=G[i−1]n+1
- 计算结果:
- 如果 d=0d = 0d=0,结果为 G[0]G[0]G[0]
- 否则,结果为 G[d]−G[d−1]G[d] - G[d-1]G[d]−G[d−1]
- 输出 nnn、ddd 和结果
复杂度分析
- 时间复杂度:O(d⋅n⋅L2)O(d \cdot n \cdot L^2)O(d⋅n⋅L2),其中 LLL 是大数的位数
- 空间复杂度:O(d⋅L)O(d \cdot L)O(d⋅L),用于存储 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 32n≤32, d≤16d \leq 16d≤16),这个解法是完全可行的。
这种递推思想在组合数学和树计数问题中非常常见,掌握这种方法对于解决类似的计数问题很有帮助。

2万+

被折叠的 条评论
为什么被折叠?



