楊輝三角是二項式係數在三角形中的一種幾何排列,中國南宋數學家楊輝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。