让我们先看看蛮力方法。
加泰罗尼亚数cn是可以组合n个上冲(╱)和n的方式的数量向下冲程(╲)而不低于地平线。根据定义,cn = (n + 2)/2 · (n + 3)/3 · ... (n + n)/n。
我们可以使用无符号的 2n 位整数来描述每个组合。 (但是,并非所有 2n 位无符号整数值都描述了有效的组合。)如果我们将非零位或设置位视为上行,而将零位视为下行,则每当下行时出现峰值上冲。 (当向下冲程之后是向上冲程时,您就有了一个山谷。)
(请注意,并非所有无符号的 2n 位整数值都描述了有效的组合。要有效,第一位必须是上行,并且必须恰好有 n i> 向上冲程。)
所以,首先编写一个函数,计算由无符号整数描述的一个组合中的峰值数量。 (请注意,因为描述组合的每个无符号整数都恰好设置了 n 位,所以我们不需要显式传递 n,只需要描述组合的无符号整数。)例如,
unsigned int peaks(unsigned int description)
{
unsigned int count = 0;
while (description) {
count += ((description & 3) == 1);
description >>= 1;
}
return count;
}
上面,从最低有效位开始读取组合。 (并且因为必须将山脉设置为可能高于地平线,所以没有偶数值描述组合。)表达式(description & 3) 隔离了最后两个有效位。四种可能的情况分别对应于双下坡、峰、谷和双上坡,按数值递增的顺序排列。峰值情况对应于值 1(二进制中的 01b:上冲后跟下冲,按重要性递增的顺序读取数字,从右到左)。在 C 中,逻辑 False 的值为 0,逻辑 True 为 1,所以在上面的循环中,我们得到了设置位为后跟(在更重要的位置)一个明确的位。
Value Mountains n Peaks
1 ╱╲ 1 1
3 ╱╱╲╲ 2 1
5 ╱╲╱╲ 2 2
7 ╱╱╱╲╲╲ 3 1
9 ╱╲╲╱ Not a valid combination
11 ╱╱╲╱╲╲ 3 2
13 ╱╲╱╱╲╲ 3 2
15 ╱╱╱╱╲╲╲╲ 4 1
17 ╱╲╲╲╱ Not a valid combination
19 ╱╱╲╲╱╲ 3 2
21 ╱╲╱╲╱╲ 3 3
23 ╱╱╱╲╱╲╲╲ 4 2
25 ╱╲╲╱╱╲ Not a valid combination
27 ╱╱╲╱╱╲╲╲ 4 2
29 ╱╲╱╱╱╲╲╲ 4 2
31 ╱╱╱╱╱╲╲╲╲╲ 5 1
33 ╱╲╲╲╲╱ Not a valid combination
35 ╱╱╲╲╲╱ Not a valid combination
37 ╱╲╱╲╲╱ Not a valid combination
39 ╱╱╱╲╲╱╲╲ 4 2
接下来,创建一个函数,该函数生成所有描述特定 n 有效组合的无符号整数值,并计算与特定峰数相对应的值。
一种蛮力方法是编写峰值计数函数,使其对所有无效组合返回 0。例如:
static unsigned int peaks(unsigned int description)
{
unsigned int count = 0;
int height = 0;
/* Description must start with an upstroke. */
if (!(description & 1))
return 0;
while (description) {
switch (description & 3) {
case 0: /* Downslope; next is downslope. */
if (--height < 0)
return 0;
break;
case 1: /* Upslope; next is downslope. */
count++;
height++;
break;
case 2: /* Downslope; next is upslope. */
if (--height < 0)
return 0;
break;
default: /* 3: Upslope; next is upslope. */
height++;
}
description >>= 1;
}
return count;
}
描述对应的n(如果peak(description) > 0)是描述中设置的位数。计数的一个绝妙技巧是
unsigned int popcount(unsigned int value)
{
unsigned int count = 0;
while (value) {
value &= value - 1;
count++;
}
return count;
}
使用这两个函数,您可以通过探索所有 2n 位无符号整数(从 0 到 (1 << (2*n)) - 1 , 包括)。
为了更好的方法,让我们检查每个 n 的峰值数量:
n Combs Occurrences*Peaks
0 1 1*0
1 1 1*1
2 2 1*1, 1*2
3 5 1*1, 3*2, 1*3
4 14 1*1, 6*2, 6*3, 1*4
5 42 1*1, 10*2, 20*3, 10*4, 1*5
6 132 1*1, 15*2, 50*3, 50*4, 15*5, 1*6
换句话说,n=6 有 132 个有效组合。其中1个峰1个,2个峰15个,3个峰50个,4个峰50个,6个峰1个。
如果我们只是形成峰值计数的整数序列,我们可以将上述表示为
1,
1, 1,
1, 3, 1
1, 6, 6, 1,
1, 10, 20, 10, 1,
1, 15, 50, 50, 15, 1,
以此类推,在 n=7 时继续 1、21、105、175、105、21、1,以及 1、28、196、490、490、196、28, 1 表示 n=8,以此类推。
如果我们对该序列进行 OEIS 搜索,我们会发现这些实际上称为 Narayana 数 T(n,k ),整个整数序列为OEIS A001263。
(注意:我不知道是这样的!我只知道我可以使用 OEIS 来确定序列是否已知,并且通常是已知的。换句话说,我不只是向您展示答案这个特殊的问题在这里,但是我如何找到 - 非常有效,如果我自己可以这么说的话 - 这类问题的解决方案,从蛮力数值方法开始。)
所以,数学上的答案是Narayana numberT(n,k) 告诉你对应的不同山脉的数量加泰罗尼亚数字 cn 恰好有 k 个峰。
如果你将binomial coefficient实现为函数binomial(n, k),那么答案就是T(n, k) = binomial(n, k) * binomial(n, k - 1) / n。
但是请注意,您可以更有效地实现 T(n, k) 作为术语乘积之间的划分(即,使用两个术语数组,并使用 greatest common divisor 辅助函数消除常用术语和术语乘积,无需由于期限取消导致的精度问题。)