让我们先看看蛮力方法。
加泰罗尼亚数c n是在不低于地平线的情况下,可以组合n次上行 (╱) 和n次下行 (╲) 的方式数。根据定义, cn = ( n + 2)/2 · (n + 3)/3 · ... (n + n)/n。
我们可以使用无符号的 2 n位整数来描述每个组合。(但是,并非所有 2 n位无符号整数值都描述了有效的组合。)如果我们将非零或设置位视为上行,而将零位视为下行,则每当下行跟随上行时就会出现峰值。(当向下冲程之后是向上冲程时,您有一个山谷。)
(请注意,并非所有无符号的 2 n位整数值都描述了有效的组合。要有效,第一位必须是上行,并且必须正好有n 个上行。)
因此,首先编写一个函数,计算由无符号整数描述的一个组合中的峰值数量。(注意,因为描述组合的每个无符号整数都设置了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(二进制中的 01 b:上冲后跟下冲,按照重要性递增的顺序读取数字,从右到左)。在 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;
}
描述对应的npeak(description) > 0
(if ) 是描述中设置的位数。计算的一个绝妙技巧是
unsigned int popcount(unsigned int value)
{
unsigned int count = 0;
while (value) {
value &= value - 1;
count++;
}
return count;
}
使用这两个函数,您可以通过探索所有 2个 n位无符号整数(从到,包括)来解决小n的所述问题。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,对于n = 8,继续使用 1、28、196、490、490、196、28、1,依此类推。
如果我们对该序列进行 OEIS 搜索,我们会发现这些实际上称为 Narayana 数T ( n , k ),整个整数序列是OEIS A001263。
(注意:我不知道是这样的!我只知道我可以使用 OEIS 来确定序列是否已知,而且它们通常是已知的。换句话说,我不只是向您展示这个特定问题的答案在这里,但是我如何找到——非常有效,如果我自己可以这么说的话——这类问题的解决方案,从蛮力数值方法开始。)
因此,数学上的答案是,纳拉亚纳数 T ( n , k ) 告诉您与加泰罗尼亚数c n对应的不同山脉的数量,其中恰好有k个峰。
如果您将二项式系数实现为函数binomial(n, k)
,那么答案是T(n, k) = binomial(n, k) * binomial(n, k - 1) / n
。
但是请注意,您可以更有效地实现术语乘积之间的除法(即,使用两个术语数组,并使用最大公约数T(n, k)
辅助函数消除常用术语和术语乘积,而不会因术语取消而导致精度问题.)