计算总组合

时间:2013-08-20 12:05:51

标签: algorithm discrete-mathematics

我不知道如何处理这个编程问题。

给定两个整数n和m,存在多少个数,使得所有数字都具有从0到n-1的所有数字,并且两个相邻数字之间的差异恰好为1,并且数字中的数字位数最多为“m”

解决此问题的最佳方法是什么?有直接的数学公式吗?

编辑:号码不能以0开头。

实施例: 对于n = 3和m = 6,有18个这样的数字(210,2101,21012,210121 ......等)

更新(有些人遇到歧义): 必须存在从0到n-1的所有位数。

4 个答案:

答案 0 :(得分:2)

此Python代码通过跟踪以特定数字结尾的数字来计算O(nm)中的答案。

不同的数组(A,B,C,D)用于跟踪已达到范围最大值或最小值的数字。

n=3
m=6
A=[1]*n # Number of ways of being at digit i and never being to min or max
B=[0]*n # number of ways with minimum being observed
C=[0]*n # number of ways with maximum being observed
D=[0]*n # number of ways with both being observed
A[0]=0 # Cannot start with 0
A[n-1]=0 # Have seen max so this 1 moves from A to C
C[n-1]=1 # Have seen max if start with highest digit
t=0
for k in range(m-1):
    A2=[0]*n
    B2=[0]*n
    C2=[0]*n
    D2=[0]*n
    for i in range(1,n-1):
        A2[i]=A[i+1]+A[i-1]
        B2[i]=B[i+1]+B[i-1]
        C2[i]=C[i+1]+C[i-1]
        D2[i]=D[i+1]+D[i-1]
    B2[0]=A[1]+B[1]
    C2[n-1]=A[n-2]+C[n-2]
    D2[0]=C[1]+D[1]
    D2[n-1]=B[n-2]+D[n-2]
    A=A2
    B=B2
    C=C2
    D=D2
    x=sum(d for d in D2)
    t+=x
print t

答案 1 :(得分:2)

在做了一些更多的研究之后,我认为毕竟可能有一种数学方法,尽管我的数学是先进的。 Douglas S. Stones向我指出了约瑟夫迈尔斯(2008)的文章BMO 2008–2009 Round 1 Problem 1—Generalisation的方向,该文章推导出用于计算矩形板上锯齿形路径数量的公式。

据我理解,在Anirudh的例子中,我们的棋盘将有6行长度为3(我相信在文章的术语中这意味着n=3r=6。我们可以看到我们的董事会:

0 1 2   example zig-zag path:  0
0 1 2                            1
0 1 2                          0
0 1 2                            1
0 1 2                              2
0 1 2                            1

由于迈尔斯公式m(n,r)将生成所有之字形路径的数字,即所有相邻数字连续且数字的所有6位数字的数字从(0,1,2)中选择,我们仍然需要确定和减去那些以零开头的那些和那些不包括所有数字的那些。

如果我理解正确,我们可以通过以下方式为我们的示例执行此操作,尽管将概念概括为任意mn可能会更复杂:

Let m(3,6) equal the number of 6-digit numbers where all adjacent digits  
are consecutive and digits are chosen from (0,1,2). According to Myers,
m(3,r) is given by formula and also equals OEIS sequence A029744 at 
index r+2, so we have 

m(3,6) = 16

How many of these numbers start with zero? Myers describes c(n,r) as the
number of zig-zag paths whose colour is that of the square in the top 
right corner of the board. In our case, c(3,6) would include the total 
for starting-digit 0 as well as starting-digit 2. He gives c(3,2r) as 2^r, 
so we have

c(3,6) = 8. For starting-digit 0 only, we divide by two to get 4.

Now we need to obtain only those numbers that include all the digits in 
the range, but how? We can do this be subtracting m(n-1,r) from m(n,r).
In our case, we have all the m(2,6) that would include only 0's and 1's,
and all the m(2,6) that would include 1's and 2's. Myers gives 
m(2,anything) as 2, so we have

2*m(2,6) = 2*2 = 4

But we must remember that one of the zero-starting numbers is included 
in our total for 2*m(2,6), namely 010101. So all together we have

m(3,6) - c(3,6)/2 - 4 + 1 
= 16 - 4 - 4 + 1 
= 9

To complete our example, we must follow a similar process for m(3,5), 
m(3,4) and m(3,3). Since it's late here, I might follow up tomorrow...

答案 2 :(得分:1)

一种方法可能是递归编程,调用函数添加以及从最后一位数中减去。

Haskell代码:

import Data.List (sort,nub)

f n m = concatMap (combs n) [n..m]

combs n m = concatMap (\x -> combs' 1 [x]) [1..n - 1] where
  combs' count result
    | count == m = if test then [concatMap show result] else []
    | otherwise  = combs' (count + 1) (result ++ [r + 1])
                ++ combs' (count + 1) (result ++ [r - 1])
   where r = last result
         test = (nub . sort $ result) == [0..n - 1]

输出:

*Main> f 3 6
["210","1210","1012","2101","12101","10121","21210","21012"
,"21010","121210","121012","121010","101212","101210","101012"
,"212101","210121","210101"]

回应Anirudh Rayabharam的评论,我希望以下代码更像'伪代码'。当总位数达到m时,如果解为哈希全部[0..n-1],则函数g输出1,否则输出0。函数f累计g的结果,用于起始位[1..n-1]和总位数[n..m]。

Haskell代码:

import qualified Data.Set as S

g :: Int -> Int -> Int -> Int -> (S.Set Int, Int) -> Int
g n m digitCount lastDigit (hash,hashCount)
  | digitCount == m = if test then 1 else 0
  | otherwise       =
      if lastDigit == 0
         then g n m d' (lastDigit + 1) (hash'',hashCount')
         else if lastDigit == n - 1
                 then g n m d' (lastDigit - 1) (hash'',hashCount')
                 else g n m d' (lastDigit + 1) (hash'',hashCount') 
                    + g n m d' (lastDigit - 1) (hash'',hashCount') 
 where test = hashCount' == n
       d' = digitCount + 1
       hash'' = if test then S.empty else hash'
       (hash',hashCount')
         | hashCount == n          = (S.empty,hashCount)
         | S.member lastDigit hash = (hash,hashCount)
         | otherwise               = (S.insert lastDigit hash,hashCount + 1)

f n m = foldr forEachNumDigits 0 [n..m] where
  forEachNumDigits numDigits accumulator = 
    accumulator + foldr forEachStartingDigit 0 [1..n - 1] where 
      forEachStartingDigit startingDigit accumulator' =
        accumulator' + g n numDigits 1 startingDigit (S.empty,0)

输出:

*Main> f 3 6
18
(0.01 secs, 571980 bytes)

*Main> f 4 20
62784
(1.23 secs, 97795656 bytes)

*Main> f 4 25
762465
(11.73 secs, 1068373268 bytes)

答案 3 :(得分:0)

将您的问题建模为2个维度中的2个叠加格子,特别是与(i,j)相互关联的((i0,j0),(i1,j1))i1 = i0 + 1, |j1 - j0| = 1,其中j > 9修改如下:

  • 删除所有对(i,j)i > m-1及其事件边缘
  • 删除所有对(i,j)((0,0), (1,1))及其事件边缘
  • 掉线(0,j), j=1..min(n-1,9)

这种结构产生了如下图所示的结构:

sample diagram for n=5, m=7 http://collapsar.ohost.de/data/so.18334599.svg

请求的数字映射到格子中的路径,从绿色元素((i,0), i=1..m-1)开始,其中包含至少一个粉红色和一个红色元素((i,n-1), i=0..m-1i)。要查看此信息,请使用点j标识给定数字的(i,j)个数字q1。包括粉红色和红色元素('极值数字')保证所有可用的diguts都以数字表示。

分析

为方便起见,让q1,q2表示位置-1。

0成为数字的第一个数字的位置min(n-1,9)q2。 如果位置0的数字为q1且vv。

,则min(n-1,9)成为数字0的位置

案例1 :第一个极值数字为0

不包含sum_{k=1..min(n-1,9)} (paths_to_0(k,1,q1)的有效前缀的数量可以表示为paths_to_0,函数paths_to_0(0,q1-1,q1) = 0; paths_to_0(1,q1-1,q1) = 1; paths_to_0(digit,i,q1) = 0; if q1-i < digit; paths_to_0(x,_,_) = 0; if x >= min(n-1,9) // x=min(n-1,9) mustn't occur before position q2, // x > min(n-1,9) not at all paths_to_0(x,_,_) = 0; if x <= 0; // x=0 mustn't occur before position q1, // x < 0 not at all and else paths_to_0(digit,i,q1) = paths_to_0(digit+1,i+1,q1) + paths_to_0(digit-1,i+1,q1); 被递归地定义为

paths_to_max(min(n-1,9),q2-1,q2) = 0;
paths_to_max(min(n-2,8),q2-1,q2) = 1;
paths_to_max(digit,i,q2)         = 0  if q2-i < n-1;
paths_to_max(x,_,_)              = 0; if x >= min(n-1,9)
                                       // x=min(n-1,9) mustn't occur before
                                       // position q2,
                                       // x > min(n-1,9) not at all
paths_to_max(x,_,_)              = 0; if x < 0;

and else  paths_to_max(digit,q1,q2) =
              paths_max(digit+1,q1+1,q2) + paths_to_max(digit-1,q1+1,q2);

同样我们有

paths_suffix(digit,length-1,length) = 2;  if digit > 0 and digit < min(n-1,9)
paths_suffix(digit,length-1,length) = 1;  if digit = 0 or  digit = min(n-1,9)
paths_suffix(digit,k,length)        = 0;  if    length > m-1
                                             or length < q2
                                             or k > length
paths_suffix(digit,k,0)             = 1;  // the empty path

and else  paths_suffix(digit,k,length) = 
             paths_suffix(digit+1,k+1,length) + paths_suffix(digit-1,k+1,length);

最后

number_count_case_1(n, m) =
    sum_{first=1..min(n-1,9), q1=1..m-1-(n-1), q2=q1..m-1, l_suffix=0..m-1-q2} (
          paths_to_0(first,1,q1)
        + paths_to_max(0,q1,q2)
        + paths_suffix(min(n-1,9),q2,l_suffix+q2)
    )   

...总计

min(n-1,9)

案例2 :第一个极值数字为min(n-1,9)

案例2.1 :初始数字不是d 这与案例1对称,所有数字min(n,10) - d都替换为number_count_case_2_1 = number_count_case_1。由于晶格结构是对称的,这意味着min(n-1,9)

案例2.2 :初始数字为q1 请注意1min(n-2,8),第二位必须为number_count_case_2_2 (n, m) = sum_{q2=1..m-2, l_suffix=0..m-2-q2} ( paths_to_max(1,1,q2) + paths_suffix(min(n-1,9),q2,l_suffix+q2) ) 。 从而

number_count ( n, m ) = 2 * number_count_case_1 (n, m) + number_count_case_2_2 (n, m);

所以盛大的总计将是

number_count

代码

我不知道是否存在use strict; use warnings; my ($n, $m) = ( 5, 7 ); # for example $n = ($n > 10) ? 10 : $n; # cutoff sub min sub paths_to_0 ($$$) { my ( $d , $at , $until ) = @_; # if (($d == 0) && ($at == $until - 1)) { return 0; } if (($d == 1) && ($at == $until - 1)) { return 1; } if ($until - $at < $d) { return 0; } if (($d <= 0) || ($d >= $n))) { return 0; } return paths_to_0($d+1, $at+1, $until) + paths_to_0($d-1, $at+1, $until); } # paths_to_0 sub paths_to_max ($$$) { my ( $d , $at , $until ) = @_; # if (($d == $n-1) && ($at == $until - 1)) { return 0; } if (($d == $n-2) && ($at == $until - 1)) { return 1; } if ($until - $at < $n-1) { return 0; } if (($d < 0) || ($d >= $n-1)) { return 0; } return paths_to_max($d+1, $at+1, $until) + paths_to_max($d-1, $at+1, $until); } # paths_to_max sub paths_suffix ($$$) { my ( $d , $at , $until ) = @_; # if (($d < $n-1) && ($d > 0) && ($at == $until - 1)) { return 2; } if ((($d == $n-1) && ($d == 0)) && ($at == $until - 1)) { return 1; } if (($until > $m-1) || ($at > $until)) { return 0; } if ($until == 0) { return 1; } return paths_suffix($d+1, $at+1, $until) + paths_suffix($d-1, $at+1, $until); } # paths_suffix # # main # number_count = sum_{first=1..min(n-1,9), q1=1..m-1-(n-1), q2=q1..m-1, l_suffix=0..m-1-q2} ( paths_to_0(first,1,q1) + paths_to_max(0,q1,q2) + paths_suffix(min(n-1,9),q2,l_suffix+q2) ) my ($number_count, $number_count_2_2) = (0, 0); my ($first, $q1, i, $l_suffix); for ($first = 1; $first <= $n-1; $first++) { for ($q1 = 1; $q1 <= $m-1 - ($n-1); $q1++) { for ($q2 = $q1; $q2 <= $m-1; $q2++) { for ($l_suffix = 0; $l_suffix <= $m-1 - $q2; $l_suffix++) { $number_count = $number_count + paths_to_0($first,1,$q1) + paths_to_max(0,$q1,$q2) + paths_suffix($n-1,$q2,$l_suffix+$q2) ; } } } } # # case 2.2 # for ($q2 = 1; $q2 <= $m-2; $q2++) { for ($l_suffix = 0; $l_suffix <= $m-2 - $q2; $l_suffix++) { $number_count_2_2 = $number_count_2_2 + paths_to_max(1,1,$q2) + paths_suffix($n-1,$q2,$l_suffix+$q2) ; } } $number_count = 2 * $number_count + number_count_2_2; 的闭合表达式,但是下面的perl代码将计算它(代码只是一个概念证明,因为它不使用memoization技术来避免重新计算已经获得的结果):

{{1}}