ZOJ1062-Tree Mode to Order(卡塔兰数+递归)

题目链接

题目大意

可以使用以下方案给二叉树编号:
空树编号为0。
单结点数编号为1。
所有m个结点的二叉树的编号小于(m+1)个结点的二叉树的编号。
若任何m个结点的二叉树,当左右子树数为L和R个时的二叉树编号为n,则下列情况下所有m个结点的二叉树,其编号都大于n:
左子树的编号大于L或者左子树编号为L,且右子树编号大于R。
序列中前10棵二叉树和编号为20的树如下
ZZOJ1062

输入

给定多个样例,每一个样例包含一个整数n,1≤n≤500 000 000。n的值为0表示输入结束(不需要输出一棵空树)

输出

对每个样例输出一行,是该实例标号对应的树。用一下方案输出树:
没有子树的树,输出为X。
有左右子树L和R,输出为(L’)X(R’),L’ 和 R’ 表示 L 和 R。
若左子树为空,只需要输出 X(R’)。
若右子树为空,只需要输出 (L’)X。

样例

Sample Input
1
20
31117532
0
Sample Output
X
((X)X(X))X
(X(X(((X(X))X(X))X(X))))X(((X((X)X((X)X)))X)X)

解题思路

katalan数

分析图例,可知,当一个树包含了m + 1个结点,那么这棵树可能的结构就有:
(左子树结点数,右子树结点数)
(0,m),
(1,m-1),
(2,m-2),
(3,m-3),
… ,
(m-1,1),
(m,0)
那么包含m+1个结点的树种类便是以上所有每种树结构包含种类的和。

这种求法符合katalan数,即
h ( n ) = h ( 0 ) ∗ h ( n − 1 ) + h ( 1 ) ∗ h ( n − 2 ) + . . . + h ( n − 1 ) ∗ h ( 0 ) ( 其 中 n ≥ 2 ) h(n) = h(0)*h(n-1) + h(1)*h(n-2) + ...+ h(n-1)*h(0) (其中n≥2) h(n)=h(0)h(n1)+h(1)h(n2)+...+h(n1)h(0)(n2)
该递推关系的解为:
h ( n ) = c ( 2 n , n ) / ( n + 1 ) h(n)=c(2n,n)/(n+1) h(n)=c(2n,n)/(n+1)

katalan数C++两种求法及katalan数的前20项:

//递归求法
void setCatalan() {
 catalan[0] = 1;
 catalan[1] = 1; 
 setC();
 for (int i = 2; i < 20; i++) {
  //递归求法
  catalan[i] = 0;
  for (int j = 0; j < i; j++) {
   catalan[i] += catalan[j] * catalan[i - 1 - j];
  }
  }
}

//公式求法
void setC() {//构造组合数
 for (int i = 0; i < 50; i++) {
  C[0][i] = 0; 
  C[i][0] = 1;
 }
 for (int i = 1; i < 50; i++) {
  for (int j = 1; j < 50; j++) {
   C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
  }
 }
}
void setCatalan() {
 catalan[0] = 1;
 catalan[1] = 1;
 setC();
 for (int i = 2; i < 20; i++) {
  //公式法
  catalan[i] = C[i*2][i] / (i + 1); }
}



/*
卡塔兰数前20项:
catalan[0] = 1
catalan[1] = 1
catalan[2] = 2
catalan[3] = 5
catalan[4] = 14
catalan[5] = 42
catalan[6] = 132
catalan[7] = 429
catalan[8] = 1430
catalan[9] = 4862
catalan[10] = 16796
catalan[11] = 58786
catalan[12] = 208012
catalan[13] = 742900
catalan[14] = 2674440
catalan[15] = 9694845
catalan[16] = 35357670
catalan[17] = 129644790
catalan[18] = 477638700
catalan[19] = 1767263190
*/

左右子树编号

通过katalan数,我们可以求出编号为n的树所包含的结点数,同时,我们也可以通过katalan数求出其左右子树的编号。
一棵树编号为n,我们可求出其结点数为m,
根据本题中树的排序为左0右m-1,左1右m-2,左 2右m-3的排列方式,且可知左0右m-1结构的种类数量为katalan[0]*katalan[m-1]
即可求出左右子树各自的数量。

设本棵树为包含m个结点的所有树里的第 A 棵树,左子树结点数为 L,右子树结点数为R。
那么,左子树的编号为:
包 含 L 个 结 点 的 第 一 棵 树 编 号 + A / k a t a l a n [ 右 子 树 结 点 数 ] 包含L个结点的第一棵树编号 + A/katalan[右子树结点数] L+A/katalan[]

右子树编号为:
包 含 R 个 结 点 的 第 一 棵 树 编 号 + A m o d ( k a t a l a n [ 右 子 树 结 点 数 ] ) 包含R个结点的第一棵树编号+A mod(katalan[右子树结点数]) R+Amod(katalan[])

递归求解

当我们可以求出左右子树编号时,就可通过递归,求出这棵树的结构,并根据题目要求将结果输出即可。

AC代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
unsigned long long C[50][50];
unsigned long long catalan[20];//卡塔兰数
unsigned long long catalanSum[20];
void setC() {//构造组合数
	for (int i = 0; i < 50; i++) {
		C[0][i] = 0; 
		C[i][0] = 1;
	}
	for (int i = 1; i < 50; i++) {
		for (int j = 1; j < 50; j++) {
			C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
		}
	}
}
void setCatalan() {
	catalan[0] = 1;
	catalanSum[0] = 0;
	catalan[1] = 1;
	catalanSum[1] = 1;
	setC();
	for (int i = 2; i < 20; i++) {
		//递归求法
		/*catalan[i] = 0;
		for (int j = 0; j < i; j++) {
			catalan[i] += catalan[j] * catalan[i - 1 - j];
		}*/
		//公式法
		catalan[i] = C[i*2][i] / (i + 1);
		catalanSum[i] = catalanSum[i - 1] + catalan[i - 1];
	}
}
void ZOJ1062(int n) { // m:左右子树结点和
	if (n == 1) {
		printf("X");
		return;
	}
	// 求编号为n的树的结点数
	int sum = 0;
	int m = 0;
	while (true) {
		if (sum + catalan[m] > n)
			break;
		sum += catalan[m];
		m++;
	}

	//求左右子树各自的结点数。
	int leftM = 0;//左子树的结点数
	int rightM = 0;//右子树的结点数
	int leftN = 0;//左子树的编号
	int rightN = 0;//右子树的编号
	while (m > 0) {
		if (sum + catalan[leftM] * catalan[m - leftM - 1] > n)
			break;
		sum += catalan[leftM] * catalan[m - leftM - 1];
		leftM++;
	}
	rightM = m - 1 - leftM;//根节点需要排除

	//计算左右子树的编号
	int a = n - sum; 
	leftN = catalanSum[leftM] + a / catalan[rightM];
	rightN = catalanSum[rightM] + a % catalan[rightM];

	//递归求出左右子树的排序
	if (leftM > 0) {
		printf("("); 
		ZOJ1062(leftN);
		printf(")");
	}
	printf("X");
	if (rightM > 0) {
		printf("(");
		ZOJ1062(rightN);
		printf(")");
	}

}
int main() {
	setCatalan();
//	for (int i = 0; i < 20; i++) {
//		cout << "catalan[" << i << "] = " << catalan[i] << endl;
//	}
	int n;
	while (scanf("%d", &n) && n) {
	//for(int i = 0 ; i < 24 ; i++){
		ZOJ1062(n);
		printf("\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值