【离散数学】高级计数技术

这是离散数学的第四篇,讨论高级计数技术。同步发布与个人博客,上一篇(【离散数学】计数/排列组合)讨论了计数以及排列组合,二项式定理等。但是仅凭排列组合等手段依然无法解决许多计数问题。这里首先讨论通过递推关系来求解计数问题,并介绍有递推关系引出的两个算法范式:动态规划和分治。这两种算法均是通过将问题分割为一系列的子问题来求解的,区别就是前者分割出来的子问题互相重叠,后者的子问题不重叠。这是两种很重要的算法,加上贪心、回溯、分支定界为五种很常用的算法。这里仅仅简单讨论思路,并分析其复杂度。关于具体的算法分析以及算法设计以后应该会讨论。而后介绍了求解一类很常见的特定递推关系——常系数线性齐次与非齐次递推关系的形式解法。并且还介绍了一种求解计数问题的很重要的手段——生成函数,这是幂级数的应用。最后介绍容斥原理,对,就是集合的容斥原理。以上内容以前均有涉及,这里再次探讨。


递推关系的应用

经典问题——汉诺塔

这是一个及其经典的问题,见汉诺塔(Tower of Hanoi),三根柱子,n个盘子,从上到下盘子从小到大。将所有n个盘子从一根柱子移动到另一根柱子,移动过程中小的盘子不能放在大的盘子下面。可以求解出所需要的最小步数,还可以编写算法打印出所有步数。

n=3的汉诺塔移动方法:

令移动n个盘子到另一个柱子所需最少次数为 H n H_n Hn ,考虑最下面一个最大的盘子,由于小的盘子不能放在大的盘子下面,所以必须首先将上面n-1个移动到另一个柱子,再将最下面的最大的一个移动到另一根柱子,再将n-1个移到其上,则完成了n个盘子的移动。则得到递推关系 H n = 2 H n − 1 + 1 H_n = 2H_{n-1}+1 Hn=2Hn1+1。初始条件很容易知道: H 0 = 0 H_0 = 0 H0=0 H 1 = 1 H_1=1 H1=1

求解递推关系:容易看出 H n + 1 = 2 ( H n − 1 + 1 ) H_n+1 = 2(H_{n-1}+1) Hn+1=2(Hn1+1),可以得到 H n = 2 n − 1 H_n= 2^n-1 Hn=2n1。可以证明这便是移动n个盘子所需的最少次数。

卡特兰数

考虑一个在n+1个数 $x_0\cdot x_1 \cdot x_2 \cdots x_n $ 的乘积中插入括号来规定乘法次序的方式数,令其为 C n C_n Cn
这里注意到一定会有一个乘法是在括号外面的(不需要加括号),假设其在 x k x_k xk x k + 1 x_{k+1} xk+1之间,则存在 C k C n − k − 1 C_kC_{n-k-1} CkCnk1种方式,考虑最后一个乘号可能取n个位置,则有 C n = ∑ k = 0 n − 1 C k C n − k − 1 C_n = \sum_{k=0}^{n-1}C_kC_{n-k-1} Cn=k=0n1CkCnk1

初始条件则为 C 0 = 1 C_0 = 1 C0=1 C 1 = 1 C_1 = 1 C1=1
利用生成函数的方法可以证明: C n = C ( 2 n , n ) n + 1 C_n = \dfrac{C(2n,n)}{n+1} Cn=n+1C(2n,n),被称作第n个卡特兰(Catalan)数,序列 { C n } \{C_n\} {Cn} 被称为卡特兰数的序列。参见 OEIS A000108,Catalan number - Wikipedia。

动态规划与递推关系

动态规划(Dynamic programming,DP)是一种算法范式,遵循动态规划范式的算法是将原问题分解为更简单的重叠的子问题,通过子问题的求解来求解原问题。常用于求解最短路线、库存管理、资源分配、设备更新、排序、装载等问题。此类问题若用分治法来解,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。所以DP通过保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而降低复杂度。以后单独讨论,这里推荐阅读:动态规划解决01背包问题,什么是动态规划?动态规划的意义是什么?。


求解线性递推关系

这部分给人的感觉相当熟悉,在高等数学中有线性微分方程的求解,线性代数中有线性方程组的求解,与这里的线性递推关系的求解十分类似,可以说是如出一辙。

求解常系数线性齐次递推关系

一个常系数的 k k k阶线性齐次递推关系指的是形如 a n = c 1 a n − 1 + c 2 a n − 2 + ⋯ + c k a n − k a_n = c_1a_{n-1}+c_2a_{n-2}+\cdots + c_ka_{n-k} an=c1an1+c2an2++ckank,的递推关系,其中 c i c_i ci为实数, c k ≠ 0 c_k \neq 0 ck̸=0

为了求解 k k k阶常系数线性齐次递推关系,这里的基本方法是寻找形如 a n = r n a_n = r^n an=rn的解,其中r是常数。递推关系要有形如 a n = r n a_n = r^n an=rn的解,则当且仅当 r r r是方程 r k − c 1 r k − 1 − c 2 r k − 2 − ⋯ − c k − 1 r − c k = 0 r^k-c_1r^{k-1}-c_2r^{k-2}-\cdots -c_{k-1}r-c_k=0 rkc1rk1c2rk2ck1rck=0的解。
将上述方程称为该递推关系的特征方程。方程的解称为特征根。(与求解常系数线性微分方程如出一辙)。特征根有可能是复数,但这里仅考虑特征根为实数的情况。

定理:假设特征方程 r k − c 1 r k − 1 − ⋯ − c k = 0 r^k-c_1r^{k-1}-\cdots -c_k=0 rkc1rk1ck=0有k个不相等的根 r 1 , r 2 , . . . , r k r_1,r_2,...,r_k r1,r2,...,rk。那么递推关系 a n = c 1 a n − 1 + c 2 a n − 2 + ⋯ + c k a n − k a_n = c_1a_{n-1}+c_2a_{n-2}+\cdots + c_ka_{n-k} an=c1an1+c2an2++ckank的解为 a n = α 1 r 1 n + α 2 r 2 n + ⋯ + α k r k n a_n = \alpha_1r_1^n+\alpha_2r_2^n+\cdots+\alpha_kr_k^n an=α1r1n+α2r2n++αkrkn

n ∈ N , α 1 , α 2 , . . . , α k n\in N,\alpha_1,\alpha_2,...,\alpha_k nN,α1,α2,...,αk是常数。
另外,对其中的每一个特征根 r i r_i ri,如果并非一重而是 m i m_i mi重时,则用 ( α i , 0 + α i , 1 n + ⋯ + α i , m i − 1 n m i − 1 ) r i n (\alpha_{i,0}+\alpha_{i,1}n+\cdots+\alpha_{i,m_i-1}n^{m_i-1})r_i^n (αi,0+αi,1n++αi,mi1nmi1)rin 替代 上述解中的 α i r i n \alpha_ir_i^n αirin即可。

例:斐波那契数列(OEIS A000045)
递推关系 f n = f n − 1 + f n − 2 f_n = f_{n-1}+f_{n-2} fn=fn1+fn2,初始条件 f 0 = 0 , f 1 = 1 f_0 = 0,f_1=1 f0=0,f1=1
特征方程 r 2 − r − 1 = 0 r^2-r-1=0 r2r1=0 根为 r 1 = ( 1 + 5 ) / 2 r_1 = (1+\sqrt{5})/2 r1=(1+5 )/2 r 2 = ( 1 − 5 ) / 2 r_2 = (1-\sqrt{5})/2 r2=(15 )/2
f n = α 1 ( 1 + 5 2 ) n + α 2 ( 1 − 5 2 ) n f_n = \alpha_1\left(\dfrac{1+\sqrt5}{2}\right)^n + \alpha_2\left(\dfrac{1-\sqrt5}{2}\right)^n fn=α1(21+5 )n+α2(215 )n代入 f 0 , f 1 f_0,f_1 f0,f1解出 α 1 = 1 5 \alpha _1 = \dfrac1{\sqrt5} α1=5 1 α 2 = − 1 5 \alpha _2 = -\dfrac1{\sqrt5} α2=5 1
则得到斐波那契数列的显式公式为 f n = 1 5 ( 1 + 5 2 ) n − 1 5 ( 1 − 5 2 ) n f_n = \dfrac1{\sqrt5}\left(\dfrac{1+\sqrt5}{2}\right)^n-\dfrac1{\sqrt5}\left(\dfrac{1-\sqrt5}{2}\right)^n fn=5 1(21+5 )n5 1(215 )n

求解常系数线性非齐次递推关系

常系数线性非齐次递推关系:形如 a n = c 1 a n − 1 + c 2 a n − 2 + ⋯ + c k a n − k + F ( n ) a_n = c_1a_{n-1}+c_2a_{n-2}+\cdots + c_ka_{n-k} +F(n) an=c1an1+c2an2++ckank+F(n),与其相伴的齐次递推关系为 a n = c 1 a n − 1 + c 2 a n − 2 + ⋯ + c k a n − k a_n = c_1a_{n-1}+c_2a_{n-2}+\cdots + c_ka_{n-k} an=c1an1+c2an2++ckank,很显然 通解 = 特解+齐次解

齐次解即对应的常系数线性齐次递推关系的解,记作 a n ( h ) a_n^{(h)} an(h),特解记作 a n ( p ) a_n^{(p)} an(p)

不同形式的F(n)具有不同形式的特解

F ( n ) F(n) F(n) 特解形式
a n + b an+b an+b c n + d cn+d cn+d
α ⋅ c n \alpha\cdot c^n αcn β ⋅ c n \beta\cdot c^n βcn

F ( n ) F(n) F(n) 形如 ( b t n t + b t − 1 n t − 1 + ⋯ + b 1 n + b 0 ) s n (b_tn^t+b_{t-1}n^{t-1}+\cdots+b_1n+b_0)s^n (btnt+bt1nt1++b1n+b0)sn.
则当 s s s 不是特征根时,特解形如 ( p t n t + p t − 1 n t − 1 + ⋯ + p 1 n + p 0 ) s n (p_tn^t+p_{t-1}n^{t-1}+\cdots+p_1n+p_0)s^n (ptnt+pt1nt1++p1n+p0)sn.
s s s m m m重特征根时,特解形如 n m ( p t n t + p t − 1 n t − 1 + ⋯ + p 1 n + p 0 ) s n n^m(p_tn^t+p_{t-1}n^{t-1}+\cdots+p_1n+p_0)s^n nm(ptnt+pt1nt1++p1n+p0)sn.


分治算法与递推关系

与动态规划相似,分治算法(Divide and conquer algorithm)范式也会将问题划分为一个或者多个小问题,不过这些小问题是不重叠的。连续使用这种划分直到可以快速找到这些小问题的解,然后将小问题的解合并为原问题的解。即三个步骤:分割原问题,解决子问题,合并得到最终解。常见简单分治算法:归并排序、二分查找。这里将说明怎样用递关系来分析分治算法的复杂度。

分治递推关系

假设一个递归算法将规模为n的问题划分为a个子问题,每个子问题规模为n/b,并且需要g(n)的额外运算来合并这些子问题。用f(n)表示求解问题规模为n的问题所需运算数,则 f ( n ) = a f ( n / b ) + g ( n ) f(n)= af(n/b)+g(n) f(n)=af(n/b)+g(n)

n = b k n = b^k n=bk,多次迭代后可以得到:
f ( n ) = a k f ( 1 ) + ∑ j = 0 k − 1 a j g ( n / b j ) f(n)= a^kf(1)+\sum_{j=0}^{k-1}a^jg(n/b^j) f(n)=akf(1)+j=0k1ajg(n/bj)

很容易知道,
二分查找的分治递推关系: f ( n ) = f ( n / 2 ) + 2 f(n)=f(n/2)+2 f(n)=f(n/2)+2
归并排序的分治递推关系: M ( n ) = 2 M ( n / 2 ) + n M(n)=2M(n/2)+n M(n)=2M(n/2)+n

分治算法的复杂度分析

定理1:设 f ( n ) f(n) f(n)是满足 f ( n ) = a f ( n / b ) + c f(n)=af(n/b)+c f(n)=af(n/b)+c 的增函数, n n n b b b整除, a ⩾ 1 a\geqslant 1 a1
b b b是大于1的整数, c c c是正实数。那么
f ( n ) = { O ( n l o g b a ) a > 1 O ( log ⁡ n ) a = 1 f(n) = \begin{cases} O(n^{log_ba})&a>1\\O(\log n)&a=1\end{cases} f(n)={O(nlogba)O(logn)a>1a=1证明:令 n = b k n=b^k n=bk即可证得,当 n ≠ b k n\neq b^k n̸=bk时,依然成立。

很容易得出二分查找复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

主定理:若 f ( n ) = a f ( n / b ) + c n d f(n)=af(n/b)+cn^d f(n)=af(n/b)+cnd,则 f ( n ) = { O ( n d ) a < b d O ( n d log ⁡ n ) a = b d O ( n l o g b a ) a ≻ b d f(n) = \begin{cases} O(n^d)&a<b^d \\O(n^d\log n)& a=b^d \\O(n^{log_ba})&a \succ b^d \end{cases} f(n)=O(nd)O(ndlogn)O(nlogba)a<bda=bdabd

同理,令 n = b k n=b^k n=bk即可证得。(ps:上面的 ≻ \succ 表示大于号,但其实这个符号并不是这个意思。这里有一个bug,hexo自带一个功能的会把一段内连续的<>之间的内容注释掉,于是就只好将就一下。)

根据主定理,很容易得出归并排序复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
可以看到,定理1只是主定理的特殊情况。
这里仅简单分析分治算法,并解决了其复杂度的问题,并未涉及分治算法的设计及具体实现。


生成函数

表示序列的一个有效方法是生成函数,把序列的项作为形式幂级数的变量x的幂的系数。可以用生成函数解决许多类型的计数问题。拓展阅读:Generating function - Wikipedia,什么是生成函数? | Matrix67: The Aha Moments。

定义

实数序列 a 0 , a 1 , . . . , a k , . . . a_0,a_1,...,a_k,... a0,a1,...,ak,...的(普通)生成函数是无穷级数 G ( x ) = ∑ k = 0 ∞ a k x k G(x)= \sum_{k=0}^{\infty}a_kx^k G(x)=k=0akxk

例:序列 { C ( n , k ) } \{C(n,k)\} {C(n,k)}的生成函数即是 G ( x ) = ( 1 + x ) n G(x)=(1+x)^n G(x)=(1+x)n.

使用生成函数求解计数问题时,通常考虑形式幂级数,即不需要考虑其收敛域(对发散或收敛并不感兴趣)

广义二项式定理

广义二项式系数:
u u u实数 k k k为非负整数,广义二项式系数定义为
( k u ) = { u ( u − 1 ) ⋯ ( u − k − 1 ) / k ! k > 0 1 k = 0 \Big(^u_k\Big)=\begin{cases}u(u-1)\cdots(u-k-1)/k! & k>0 \\1&k=0\end{cases} (ku)={u(u1)(uk1)/k!1k>0k=0

当u为负整数时,展开即可有下列式子成立 ( k − n ) = ( − 1 ) r C ( n + r − 1 , r ) \Big(^{-n}_k\Big) = (-1)^rC(n+r-1,r) (kn)=(1)rC(n+r1,r)

广义二项式定理:
x x x是实数, ∣ x ∣ < 1 |x|<1 x<1 u u u 是实数,那么 ( 1 + x ) u = ∑ k = 0 ∞ ( k u ) x k (1+x)^u=\sum_{k=0}^{\infty}\Big(^u_k\Big)x^k (1+x)u=k=0(ku)xk

其实这就是 ( 1 + x ) u (1+x)^u (1+x)u的幂级数展开,使用麦克劳林级数(即在x=0处泰勒展开)即可证明。

常用生成函数

这里给出一些最常用生成函数,以及其对应的序列一般项。

(1). ( 1 + a x ) n = ∑ k = 0 n C ( n , k ) a k x k (1+ax)^n=\sum_{k=0}^nC(n,k)a^kx^k (1+ax)n=k=0nC(n,k)akxk a k = C ( n , k ) a k a_k=C(n,k)a^k ak=C(n,k)ak,二项式定理得到
(2). 1 − x r + 1 1 − x = ∑ k = 0 n x k \dfrac{1-x^{r+1}}{1-x}=\sum_{k=0}^nx^k 1x1xr+1=k=0nxk a k = 1 , k ⩽ n a^k=1,k\leqslant n ak=1,kn;否则为0,几何级数求和得到
(3). 1 1 − a x = ∑ k = 0 ∞ a k x k \dfrac1{1-ax}=\sum_{k=0}^{\infty}a^kx^k 1ax1=k=0akxk a k = a k a_k=a^k ak=ak,对 ∣ x ∣ < 1 |x|<1 x<1的几何级数求和取极限得到
(4). 1 ( 1 − x ) 2 = ∑ k = 0 ∞ ( k + 1 ) x k \dfrac 1{(1-x)^2}=\sum_{k=0}^{\infty}(k+1)x^k (1x)21=k=0(k+1)xk a k = k + 1 a_k=k+1 ak=k+1,对 1 1 − x \dfrac1{1-x} 1x1求导得到
(5). 1 ( 1 − x ) n = ∑ k = 0 ∞ C ( n + k − 1 , k ) x k \dfrac1{(1-x)^n}=\sum_{k=0}^{\infty}C(n+k-1,k)x^k (1x)n1=k=0C(n+k1,k)xk a k = C ( n + k − 1 , k ) = C ( n + k − 1 , n − 1 ) a_k=C(n+k-1,k)=C(n+k-1,n-1) ak=C(n+k1,k)=C(n+k1,n1),由广义二项式定理得到
(6). e x = ∑ k = 0 ∞ x k k ! e^x=\sum_{k=0}^{\infty}\dfrac{x^k}{k!} ex=k=0k!xk a k = 1 / k ! a_k=1/k! ak=1/k!,泰勒展开即可得到
(7). ln ⁡ ( 1 + x ) = ∑ k = 0 ∞ ( − 1 ) k + 1 k x k \ln(1+x)=\sum_{k=0}^{\infty}\dfrac{(-1)^{k+1}}{k}x^k ln(1+x)=k=0k(1)k+1xk a k = ( − 1 ) k + 1 / k a_k={(-1)^{k+1}}/{k} ak=(1)k+1/k,同上泰勒展开得到

生成函数可以用来求解计数问题,还可以用来求解递推关系和证明组合恒等式。这里不想写了,略过吧!(笑)


容斥原理及其应用

两个集合的容斥原理是很熟悉的, ∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ |A\cup B|=|A|+|B|-|A\cap B| AB=A+BAB,那么对于多个几个呢?很容易想到,但可能并不容易写出来。Inclusion–exclusion principle。

容斥原理

A 1 , A 2 . . . . , A n A_1,A_2....,A_n A1,A2....,An是有穷集,则 ∣ A 1 ∪ A 2 ∪ ⋯ ∪ A n ∣ = ∑ 1 ⩽ i ⩽ n ∣ A i ∣ − ∑ 1 ⩽ i < j ⩽ n ∣ A i ∩ A j ∣ + ∑ 1 ⩽ i < k < j ⩽ n ∣ A i ∩ A j ∩ A k ∣ + ⋯ + ( − 1 ) n + 1 ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A n ∣ |A_1\cup A_2 \cup \cdots \cup A_n|=\sum_{1\leqslant i \leqslant n}|A_i|-\sum_{1\leqslant i< j \leqslant n}|A_i\cap A_j|\\ +\sum_{1\leqslant i<k< j \leqslant n}|A_i\cap A_j\cap A_k|+\cdots+(-1)^{n+1}|A_1\cap A_2\cap\cdots \cap A_n| A1A2An=1inAi1i<jnAiAj+1i<k<jnAiAjAk++(1)n+1A1A2An

可以看到加号和减号是交替出现的,保证了没有遗漏也没有重复。

三个集合的容斥原理:

应用很多,此处愉快地略过。


结语

其实我只想快速看完这本书,写完了之后,好好想一想应该怎样去写。还有慢慢肝算法导论,重新回顾数据结构,一堆技术书等着去肝呢!所有省略了很多内容,对自己还未掌握的东西抄上来就没有多大意义了。给了很多链接,但其实大部分链接我都未曾去认真看过(笑)。

逐步积累,随性记录,知道自己要去的地方、要走的路,一直写着就好。——201.3.7


参考资料:《离散数学及其应用》(本科教学版,Kenneth H.Rosen著,原书第七版)

你可能感兴趣的:(离散数学,离散数学,计算机科学,分治,计数)