最近做了一道算法题,觉得很有意思。记录如下:
题目大意
管道取珠
有一个游戏,左侧有两个上下两个管道,右侧有一个输出管道。游戏初始时,左侧上下两个管道分别有一定数量的小球(有深色球和浅色球两种类型),而右侧输出管道为空。每一次操作,可以从左侧选择一个管道,并将该管道中最右侧的球推入右边输出管道。
假设上管道中有n个球,下管道中有m个球,则整个游戏过程需要进行n+m次操作,即将所有左侧管道中的球移入输出管道。最终n +m个球在输出管道中从右到左形成输出序列。
爱好数学的小X知道,他共有C(n+m,n)种不同的操作方式,而不同的操作方式可能导致相同的输出序列。举个例子
我们用A表示浅色球,B表示深色球。并设移动上管道右侧球的操作为U,移动下管道右侧球的操作为D,则共有C(2+1,1)=3种不同的操作方式,分别为UUD, UDU, DUU;最终在输出管道中形成的输出序列(从右到左)分别为BAB,BBA,BBA。可以发现后两种操作方式将得到同样的输出序列。
假设最终可能产生的不同种类的输出序列共有K种,其中第i种输出序列的产生方式(即不同的操作方式数目)有ai个。聪明的小X早已知道,
因此,小X希望计算得到
你能帮助他计算这个值么?由于这个值可能很大,因此只需要输出该值对1024523的取模即可(即除以1024523的余数)。
说明:文中C(n+m, n)表示组合数。组合数C(a,b)等价于在a个不同的物品中选取b个的选取方案数。
题解:
方法一
按照问题的要求,最容易想到的就是求出每种不同序列的取数方法,最后求出平方和。
- 具体如何求出每种序列的取法数?由于只有两种珠子,可以使用二进制0和1表示每种珠子。具体可以构建一个数组或者任何一种线性存储结构,其下标对应每种序列的二进制表示,数组的值对应取数方法。采用递归遍历每种取珠方法。算法复杂度为O(2^(m+n))
方法二
利用将乘法转化加法的思想对问题做转换。
定义变量A表示取出所有m+n个珠子的一种取珠方法
定义变量B表示取出所有m+n个珠子的一种取珠方法
定义(A)表示通过A方法取得序列结果
定义(B)表示通过B方法取得的序列结果
T(A,B) 表示所有满足(A)==(B)的(A,B)序列对的个数。其中A与 B互相独立。
可以看得出T(A,B)即要求的结果。
这种表示方法本质上是将乘法转化为加法,因为假如某个序列结果X其有n种取珠方法 X1、X2…..Xn那么其对应的(A,B)序列对就有个。那所有的序列结果其对应的(A,B)序列对个数之和就是待求结果。
问题是如何求T(A,B)?
问题的关键就是把所有的满足(A)==(B)的(A,B)序列对个数求出来,又要判断两个序列结果是否相同,又要计算生成序列方法数,那么这个序列对个数一定要和取珠得过程相关联,而一个取珠的过程的某个状态可以表示为上面的管道取了a个,下面的管道取了b个。因此可以用[a,b]表示一个取球的状态,[m,n]即表示已经取完所有球,将其扩展位一个序列对(a,b,c,d)表示一个二元组的取球序列状态,F[a,b,c,d]{其中a+b=c+d}表示所有满足(a+b)==(c+d)的(a+b,c+d)序列对个数。那么F[m,n,m,n]即为所求的结果。F[0,0,0,0]=0
F[i,j,k,l]递推方程
F[i+1,j,k+1,l] = F[i+1,j,k+1,l] +F[i,j,k,l] (上管道i+1个球等于上管道k+1个球)
F[i,j+1,k+1,l] = F[i,j+1,k+1,l] +F[i,j,k,l] (下管道j+1个球等于上管道k+1个球)
F[i+1,j,k,l+1] = F[i+1,j,k,l+1] +F[i,j,k,l] (上管道i+1个球等于下管道l+1个球)
F[i,j+1,k,l+1] = F[i,j+1,k,l+1] +F[i,j,k,l] (下管道j+1个球等于下管道l+1个球)
状态个数,因此复杂度为。因为需要满足(a+b)==(c+d)因此又可以减少一维,复杂度为O(m^2*n),。
很多人将该问题归结为DP题,但是我认为该问题于DP关系不大,他并不涉及重复子问题,以及状态无后效性。只是一个巧妙的递推计数。
该算法能够将算法复杂度由O(2^(m+n))降至O(m^2*n),关键在于将乘法转化为加法,从而避开了计算每种取珠结果对应的取法数这个问题。