從「楊輝三角形」談起

      楊輝三角是二項式係數在三角形中的一種幾何排列,中國南宋數學家楊輝1261年所著的《詳解九章算法》一書中出現。在歐洲,帕斯卡(1623~1662)在1654年發現這一規律,因此這個表又叫作帕斯卡三角形。帕斯卡的發現比楊輝要遲393年。php

      若是將(a+b)n(n爲非負整數)的每一項按字母a的次數由小到大排列,就能夠獲得下面的等式:html

      (a+b)0=1 ,    它只有一項,係數爲1;
      (a+b)1=a+b ,它有兩項,係數分別是1,1;
      (a+b)2=a2+2ab+b2,它有三項,係數分別是1,2,1;
      (a+b)3=a3+3a2b+3ab2+b3,它有四項,係數分別是1,3,3,1;
       ……算法

      由此,可得下面的圖表,這個圖表就是楊輝三角形。編程

       觀察上圖表,咱們發現每一行的首末都是1,而且下一行的數比上一行多1個,中間各數都寫在上一行兩數中間,且等於它們的和,能夠按照這個規律繼續將這個表寫下去。數組

【例1】楊輝三角形。網絡

      輸入n(1<=n<=30),輸出楊輝三角形的前n行。ide

      (1)編程思路1。ui

      用一個二維數組 y[31][31] 來保存楊輝三角形每一行的值。楊輝三角形第row行能夠由第row-1行來生成。this

例如:spa

數組元素

Y[row][1]

Y[row][2]

Y[row][3]

Y[row][4]

Y[row][5]

Row=4行

1

3

3

1

0

Row=5行

1

4

6

4

1

      由上表知:當row=5時,              y[5][1] = 1,

                y[5][2] = y[4][1] + y[4][2],   y[5][3] = y[4][2] + y[4][3],

                y[5][4] = y[4][3] + y[4][4] ,  y[5][5] = y[4][4] + y[4][5]

      通常的,對於第row(1~30)行,該行有row+1個元素,其中:

      y[row][1]=1

      第col(2~row+1)個元素爲:  y[row][col] = y[row-1][col-1] + y[row-1][col]。

      (2)源程序1。

#include <stdio.h>
int main()
{
      int n,i,j,y[31][31]={0};
      for (i=1;i<=30;i++)        // 賦行首與行尾元素值爲1
           y[i][1]=y[i][i]=1;
      for (i=3;i<=30;i++)        // 每行中間元素賦值
           for (j=2;j<i;j++)
               y[i][j]=y[i-1][j-1]+y[i-1][j];
      while (scanf("%d",&n)!=EOF)
      {
           for (i=1;i<=n;i++)
           {
                 for (j=1;j<=i;j++)
                 {
                      if (j!=1) printf(" ");
                          printf("%d",y[i][j]);
                 }
                 printf("\n");
            }
            printf("\n");
      }
      return 0;
}

      (3)編程思路2。

      用一個一維數組 y[30] 來保存楊輝三角形某一行的值。楊輝三角形第row行能夠由第row-1行來生成。

      例如:

數組元素

Y[0]

Y[1]

y[2]

Y[3]

Y[4]

Row-1=3行

1

3

3

1

0

Row=4行

1

4

6

4

1

      由上表知:當row=4時,y[4] = y[4]+y[3],    y[3] = y[3]+y[2],

                        y[2] = y[2]+y[1] ,   y[1] = y[1]+y[0],

                       y[0]=1

      通常的,對於第row(0~9)行,該行有row+1個元素,

       第col(row~1)個元素爲:  y[col]=y[col]+y[col-1],

                               y[0]=1

      (4)源程序2。

#include <stdio.h>
#include <string.h>
int main()
{
      int y[30],row,col,n;
      while (scanf("%d",&n)!=EOF)
      {
            memset(y,0,sizeof(y)); // 數組元素初始化爲0
            y[0]=1;
            printf("%d\n",y[0]);
            for (row=1;row<n;row++)
            {
                 for (col=row;col>=1;col--)
                      y[col]=y[col]+y[col -1];
                 for (col=0;col<=row;col++)
                 {
                       if (col!=0) printf(" ");
                       printf("%d",y[col]);
                 }
                 printf("\n");
            }
            printf("\n");
      }
      return 0;
}

      將上面的兩個源程序提交給HDU 2032「楊輝三角」,都可以Accepted。

      下面咱們進一步討論一下楊輝三角形。

      咱們根據楊輝三角形前16行中每一個數的奇偶性決定是否輸出一個特定字符。好比若是是奇數,輸出一個「*」號;是偶數,輸出一個空格。編寫以下的程序:

#include <stdio.h>
int main()
{
      int n,i,j,y[17][17]={0};
      for (i=1;i<=16;i++) // 賦行首與行尾元素值爲1
          y[i][1]=y[i][i]=1;
      for (i=3;i<=16;i++) // 每行中間元素賦值
           for (j=2;j<i;j++)
                y[i][j]=y[i-1][j-1]+y[i-1][j];
      for (i=1;i<=16;i++)
      {
             for (j=1;j<=i;j++)
                  if (y[i][j]%2==1) printf("* ");
                  else printf(" ");
             printf("\n");
      }
      return 0;
}

      運行上面的程序,能夠獲得以下的運行結果。

      

       運行結果的圖形是一個遞歸深度爲4的三角形。 經過這個圖形,咱們感受楊輝三角形中每一個數字的奇偶應該知足必定的規律。

     組合數C(n,m)是指從n個元素中選出m個元素的全部組合個數。其通用計算公式爲:

         C(n,m)=n!/[m!*(n-m)!]    C(0,0)=1   C(1,0)=1   C(1,1)=1 
     從n個元素中取m個元素,考慮第n個元素,有兩種狀況:(1)不取。則必須在前n-1個元素中取m個元素,方案數爲C(n-1,m);(2)取。則只需在前n-1個元素中取m-1個元素,方案數爲C(n-1,m-1)。所以,   C(n,m)=C(n-1,m)+C(n-1,m-1)   

       這正好符合楊輝三角形的遞推公式。 即 楊輝三角中第i行第j列的數字正是C(i,j)的結果。所以,下面對楊輝三角形中各行各列數字的討論轉化爲對組合數C(n,m)的討論。

【例2】組合數的奇偶性。 (POJ 3219)

      二項式係數C(n, m)因它在組合數學中的重要性而被普遍地研究。二項式係數能夠以下遞歸的定義:

      C(1, 0) = C(1, 1) = 1;
      C(n, 0) = 1   對於全部n > 0;
      C(n, m) = C(n-1, m-1) + C(n-1, m)   對於全部0 < m ≤ n。

      給出n和k,肯定C(n, m)的奇偶性。

      (1)編程思路1。
       對於給定C(n,m),檢查n!中2因子的個數與m!和(n-m)!中2因子個數和的關係,假設n!中2因子個數爲a,m!中2因子個數爲b,(n-m)!中2因子個數爲c,則顯然有a>=(b+c);而且當a==b+c時,必定爲奇,不然爲偶。

       (2)源程序1。

#include <stdio.h>
int getTwo(int x)     // x!中2的因子的個數
{
      int cnt=0;
      while (x/2!=0)
      {
           cnt += x/2;
           x=x/2;
       }
       return cnt;
}
int main()
{
      int n,k;
      while (scanf("%d%d", &n,&k)!=EOF)
      {
            if (getTwo(n)-getTwo(k)-getTwo(n-k)>0)
                 printf("0\n");
            else
                 printf("1\n");
      }
      return 0;
}

       (3)編程思路2。

      前面經過楊輝三角形中數字的奇偶性輸出「*」圖時,咱們感受其數字的奇偶性與數字所在的行號和列號有必定的關係,即組合數C(n,m)的奇偶性與n和m有對應關係。

      根據網絡上的資料,給出結論以下:

  組合數的奇偶性斷定方法爲:
  對於C(n,m),若n&m == m  則C(n,m)爲奇數,不然爲偶數。
  證實:        // 下面的證實採用數學概括法,若是沒興趣,跳過便可,知道結論好了!

  由C(n,m) = C(n-1,m) + C(n-1,m-1);
  對應於楊輝三角:
  1
  1 2 1
  1 3 3 1
  1 4 6 4 1
  ………………
  能夠驗證前面幾層及m = 0時知足結論,下面證實在C(n-1,m)和C(n-1,m-1) (m>0) 知足結論的狀況下,C(n,m)知足結論。
  1)假設C(n-1,m)和C(n-1,m-1)爲奇數:
  則有:(n-1)&m == m;
             (n-1)&(m-1) == m-1;
  因爲m和m-1的最後一位(在這裏的位指的是二進制的位,下同)必然是不一樣的,因此n-1的最後一位必然是1。
  現假設 n&m == m。
  則一樣由於n-1和n的最後一位不一樣推出m的最後一位是1。
  由於n-1的最後一位是1,則n的最後一位是0,因此n&m != m,與假設矛盾。
  因此得  n&m != m。
  2)假設C(n-1,m)和C(n-1,m-1)爲偶數:
  則有:(n-1)&m != m;
             (n-1)&(m-1) != m-1;
  現假設n&m == m.
  則對於m最後一位爲1的狀況:
  此時n最後一位也爲1,因此有(n-1)&(m-1) == m-1,與假設矛盾。
  而對於m最後一位爲0的狀況:
  則m的末尾必有一部分形如:10; 表明任意個0。
  相應的,n對應的部分爲: 1{*}*; *表明0或1。
  而若n對應的{*}*中只要有一個爲1,則(n-1)&m == m成立,因此n對應部分也應該是10。
  則相應的,m-1和n-1的末尾部分均爲01,因此(n-1)&(m-1) == m-1 成立,與假設矛盾。
  因此得 n&m != m。
  由1)和2)得出當C(n,m)是偶數時,n&m != m。
  3)假設C(n-1,m)爲奇數而C(n-1,m-1)爲偶數:
  則有:(n-1)&m == m;
             (n-1)&(m-1) != m-1;
  顯然,m的最後一位只能是0,不然由(n-1)&m == m便可推出(n-1)&(m-1) == m-1。
  因此m的末尾必有一部分形如:10;
  相應的,n-1的對應部分爲: 1{*}*;
  相應的,m-1的對應部分爲: 01;
  則若要使得(n-1)&(m-1) != m-1 則要求n-1對應的{*}*中至少有一個是0.
  因此n的對應部分也就爲 : 1{*}*; (不會由於進位變1爲0)
  因此 n&m = m。
  4).假設C(n-1,m)爲偶數而C(n-1,m-1)爲奇數:
  則有:(n-1)&m != m;
             (n-1)&(m-1) == m-1;
  分兩種狀況:
  當m-1的最後一位爲0時:
  則m-1的末尾必有一部分形如: 10;
  相應的,m的對應部分爲 : 11;
  相應的,n-1的對應部分爲 : 1{*}0; (若爲1{*}1,則(n-1)&m == m)
  相應的,n的對應部分爲 : 1{*}1;
  因此n&m = m。
  當m-1的最後一位爲1時:
  則m-1的末尾必有一部分形如: 01; (前面的0能夠是附加上去的)
  相應的,m的對應部分爲 : 10;
  相應的,n-1的對應部分爲 : 01; (若爲11,則(n-1)&m == m)
  相應的,n的對應部分爲 : 10;
  因此n&m = m。
  由3),4)得出當C(n,m)爲奇數時,n&m = m。
  綜上,結論得證!

     (4)源程序2。

#include <stdio.h>
int main()
{
      int n,k;
      while (scanf("%d%d", &n,&k)!=EOF)
      {
            if ((n&k)==k)
                 printf("1\n");
           else
                 printf("0\n");
      }
      return 0;
}

       根據組合數的奇偶性斷定方法:  對於C(n,m),若n&m == m  則C(n,m)爲奇數,不然爲偶數。

       能夠寫出以下一個程序。

#include <stdio.h>
int main()
{
      int n,i,j;
      while (scanf("%d",&n) && n!=0)
      {
            for (i=0;i<(2<<(n-1));i++)
            {
                 for (j=0;j<=i;j++)
                      if ((i&j)==j) printf("* ");
                      else printf(" ");
                 printf("\n");
           }
      }
      return 0;
}

      運行這個程序,輸入4,能夠獲得前面所示的星號圖形。有一次,我在網上隨意瀏覽時,發現上面這個程序,當時以爲有些奇妙,有些小神奇。由於,要輸出一個遞歸形式的星號圖形,我習慣性地採起遞歸的方法。例如,爲達到上面程序的功能,根據輸入的n,輸出相應的遞歸圖形,我會編寫以下的程序:

#include <stdio.h>
#define N 64
void draw(char a[][N], int n, int row, int col)
{
      if(n==1)
      {
           a[row][col] = '*';
           return;
      }
      int w = 1;
      int i;
      for(i=1; i<=n-2; i++) w *= 2;
      draw(a, n-1, row, col);
      draw(a, n-1, row+w, col+w);
      draw(a, n-1, row+w,col);
}
int main()
{
     char a[N][N];
     int n,w,i,j;
     while (scanf("%d",&n) && n!=0)
     {
            for(i=0;i<N;i++)
                for(j=0;j<N;j++)
                     a[i][j] = ' ';
            w=1;
            for(i=1; i<=n-1; i++) w *= 2;
            draw(a,n,0,0);
            for(i=0; i<w; i++)
            {
                  for(j=0; j<w; j++)
                      printf("%c ",a[i][j]);
                  printf("\n");
            }
      }
      return 0;
}

      一個簡單的二重循環便可完成遞歸圖形的描繪,我當時還琢磨半天,怎麼會這樣?怎麼想出來的?怎麼會這樣,我如今明白了,組合數的奇偶性判斷規則。怎麼想出來的,也只能歸結於小神奇了,畢竟組合數的奇偶性剛好和一個遞歸圖形完美結合起來,單靠想是難想出來的。固然,對大牛們可能也簡單,我就呵呵了!

      關於遞歸圖形的構造輸出,有興趣可看看個人另外一篇隨筆:遞歸(五):遞歸圖形。下面採用二重循環的方法實現該隨筆中例2的遞歸圖形的輸出。

【例3】一個遞歸圖形。

       小明在X星球的城堡中發現了以下圖形:

            

        編寫一個程序,實現該圖形的打印。

        (1)編程思路。

       設row表明行號,col表明列號。用組合數的奇偶性判斷規則,若是是奇數(row & col ==col),輸出」*「;若是是偶數,就輸出空格。

       輸入n(表明度,即遞歸深度,題幹中給出的兩個圖形的都分別爲4和6),輸出的行數row=2n-1。因爲最後一行抵左端,從下往上每行向後縮進一個位置(經過輸出空格實現)。所以,第row行應先輸出的空格數爲 2n-1-row-1。

      (2)源程序。

#include <stdio.h>
int main()
{
      int n,i,w,row,col;
      while (scanf("%d",&n) && n!=0)
      {
            w=1;
            for (i=1; i<=n-1; i++) w *= 2;
            for (row=0; row<w; row++)
            {
                   for (col=1; col<w-row; col++)   // 完成縮進
                             printf(" ");
                   for (col=0;col<=row;col++)
                          if ((row & col)==col)
                                printf("* ");
                          else
                                printf(" ");
                   printf("\n");
           }
      }
      return 0;
}

      楊輝三角形做爲二項式係數有着重要的應用價值。熟練地構造出楊輝三角形的各項(見例1的源程序),能夠用來解決實際問題。

【例4】新生晚會 (HDU 2519)。

Problem Description
開學了,杭電又迎來了好多新生。ACMer想爲新生準備一個節目。來報名要表演節目的人不少,多達N個,可是隻須要從這N我的中選M個就夠了,一共有多少種選擇方法?
Input
數據的第一行包括一個正整數T,接下來有T組數據,每組數據佔一行。
每組數據包含兩個整數N(來報名的人數,1<=N<=30),M(節目須要的人數0<=M<=30)
Output
每組數據輸出一個整數,每一個輸出佔一行
Sample Input
5
3 2
5 3
4 4
3 6
8 0
Sample Output
3
10
1
0
1

      (1)編程思路。

      本題實質求組合數C(n,m)的值。構造一個楊輝三角形便可。

      (2)源程序。

#include <stdio.h>
int main()
{
      int n,m,i,j,t,y[31][31]={0};
      for (i=1;i<=30;i++)           // 賦行首與行尾元素值爲1
               y[i][0]=y[i][i]=1;      // 注意列標從0開始
      for (i=2;i<=30;i++) // 每行中間元素賦值
             for (j=1;j<i;j++)
                  y[i][j]=y[i-1][j-1]+y[i-1][j];
      scanf("%d",&t);
      while (t--)
      {
              scanf("%d%d",&n,&m);
              printf("%d\n",y[n][m]);
       }
       return 0;
}

【例5】Code (POJ 1850)。

Description

Transmitting and memorizing information is a task that requires different coding systems for the best use of the available space. A well known system is that one where a number is associated to a character sequence. It is considered that the words are made only of small characters of the English alphabet a,b,c, ..., z (26 characters). From all these words we consider only those whose letters are in lexigraphical order (each character is smaller than the next character).

The coding system works like this:
• The words are arranged in the increasing order of their length.
• The words with the same length are arranged in lexicographical order (the order from the dictionary).
• We codify these words by their numbering, starting with a, as follows:
a - 1
b - 2
...
z - 26
ab - 27
...
az - 51
bc - 52
...
vwxyz - 83681
...

Specify for a given word if it can be codified according to this coding system. For the affirmative case specify its code.
Input

The only line contains a word. There are some constraints:
• The word is maximum 10 letters length
• The English alphabet has 26 characters.
Output

The output will contain the code of the given word, or 0 if the word can not be codified.
Sample Input

bf
Sample Output

55

      (1)編程思路。

      題目的意思是:已知26個英文字母的組合和數值的對應關係,如a~z表示第1~26列,ab~az表示第27~51,…。 輸入字母組成的字符串str,問它對應的整數爲多少。

       首先判斷輸入的str是不是升序序列,若是不是升序序列,則輸入不合法,直接輸出 0。

       若是是升序序列,則先計算比str長度少的全部字符串個數。

       假設str爲 vwxyz ,其長度爲5。則

      長度爲1的字符串 有 a,b,c,…,y,z  共 C(26,1)=26 個。

      長度爲2的字符串

              以a開頭的 有 ab,ac,ad,…,ay,az  共 C(25,1)=25個;

              以b開頭的 有 bc,bd,…,by,bz       共 C(24,1)=24個;

 

              以x開頭的 有 xy,xz                       共 C(2,1)=2個;

              以y開頭的 有 yz                           共 C(1,1)=1個。

       由數學公式:

            

      知,長度爲2的字符串共有 C(1,1)+C(2,1)+…+C(24,1)+C(25,1)=C(26,2) 個。

      同理,長度爲3的字符串共有 C(26,3) 個。

                長度爲4的字符串共有 C(26,4) 個。

      所以,長度比5小的字符串的總數爲: C(26,1)+C(26,2)+C(26,3)+C(26,4)=26+325+2600+14950=17901。

      而後,從高位到低位處理長度爲5,但比str小的字符串的個數。

     首位爲」v「,所以,首位爲a,b,…,u的字符串均比str小。

            首位爲 a 的字符串有 abcde,abcdf,  …,awxyz,共 C( 25,4)個,即後4個字母能夠在b~z這25個字母中任取4個。

            首位爲 b 的字符串有 bcdef,bcdeg,  …,bwxyz,共 C( 24,4)個,即後4個字母能夠在c~z這24個字母中任取4個。

                ……

            首位爲 u 的字符串有 uvwxy,uvwxz,uvwyz,uvxyz,uwxyz,共 C(5,4)個,即後4個字母能夠在v~z這5個字母中任取4個。

      所以,考慮首位後,比str小的字符串個數有

        C(25,4)+C(24,4)+……+C(6,4)+C(5,4)=12650+10626+8855+7315+5985+4845+3876+3060+2380+1820+1365

                        +1001+ 715+ 495+ 330+ 210+ 126+ 70+ 35+ 15+ 5=65779

       次位爲」w「,由於首位爲v,次位爲w,後3位的又都要比w大,不然不知足升序,所以只有xyz可選,考慮次位後,比str小的字符串個數爲0。

       同理,考慮第3位、第4位及最後1位,比str小的字符串個數均爲0。

      故 vwxyz 對應的數字爲: 17901+65779+1(表明自身)=83681。  與題幹一致。

       (2)源程序。

#include <stdio.h>
#include <string.h>
int main()
{
      char s[15],ch,t;
      int c[27][27],len,ans=1,flag;
      int i,j;
      for (i=1;i<=26;++i)
      {
               c[i][0]=1; c[i][i]=1;
              for (j=1;j<i;++j)
                   c[i][j]=c[i-1][j]+c[i-1][j-1];
      }
      scanf("%s",s);
      len=strlen(s);
      flag=1;
      for (i=1;i<len;i++)        // 檢查輸入的字符串是否爲升序,不是則輸入不合法,輸出0
              if (s[i]<=s[i-1])
              {
                    flag=0;
                    break;
              }
      if (flag==0)
            printf("0\n");
      else
      {
               for (i=1;i<len;++i)       // 長度比該串短的先加上
                    ans+=c[26][i];
               for(i=0;i<len;i++)       // 從高位進行處理對於每一位處理到該位的前一個,好比該位爲‘d',就處理到c
               {
                       ch=(i==0? 'a':(s[i-1]+1));
                       for (t=ch;t<s[i];t++)
                             ans+=c['z'-t][len-1-i];
               }

                       printf("%d\n",ans);
               }
              return 0;
       }

      POJ  1496 」Word Index「與本題相似,在理解了本題後,能夠順手經過POJ 1496。

相關文章
相關標籤/搜索