求楊輝三角第n行的第m個數演算法的深入研究
楊輝三角:
首先普及一下一些我們並不需要了解的知識(只是想裝一下逼罷了...).
楊輝三角,之所以叫楊輝三角,是因為他在我國數學家楊輝的一本名為《詳解九章演算法》裡出現過,所以後人以他的名義命名,稱之為楊輝三角形.
楊輝三角有非常多有趣的性質:
例如第n行上的數字之和就等於2^(n-1).
每個數等於他上面的兩個數之和.
第N行的第m個數與第n行的第n-m+1個數相等.
第n行有n個數.
……
and so on.......
然而今天,我要討論的並非這些性質,而是一個最最基本的問題——如何快速知道第n行第m個數的和——假設有這麼一道題目,且資料範圍如下:
Way one:
只要按楊輝三角最普通的一個數字等於其左上角、右上角兩數之和的性質去模擬即可,再用高精度就可以拿40分了.
Way two:
拿到40分之後,想繼續前進就得明白一個最基礎的楊輝三角定律:楊輝三角形的第n行的m個數可表示為 C(n-1,m-1).
知道這一簡單定律之後,我們就可以根據這一定律計算組合所對應的值,再加高精度,可以做到90分左右,但還是有一個極限資料過不了.
程式碼:
var s:string; a,c,ans:array[1..100000] of longint; n,m,i,j,k,len,d:longint; begin readln(n,m); if (m=1)or(m=n) then begin writeln(1); halt; end; dec(n); a[1]:=n; len:=1; d:=n; for i:=1 to m-2 do begin d:=d-1; str(d,s); fillchar(c,sizeof(c),0); for j:=1 to len do begin c[j]:=c[j]+a[j]*d; c[j+1]:=c[j+1]+c[j] div 10000; c[j]:=c[j] mod 10000; end; len:=len+length(s); while (c[len]=0) and (len>1) do dec(len); for j:=1 to len do a[j]:=c[j]; end; if m=2 then ans:=a; for j:=2 to m-1 do begin d:=0; for i:=len downto 1 do begin ans[i]:=(d*10000+a[i]) div j; d:=(d*10000+a[i]) mod j; end; while (ans[len]=0)and(len>1) do dec(len); for i:=1 to len do a[i]:=ans[i]; end; write(ans[len]); for i:=len-1 downto 1 do if ans[i]>=1000 then write(ans[i]) else if ans[i]>=100 then write('0',ans[i]) else if ans[i]>=10 then write('00',ans[i]) else write('000',ans[i]); end.
Way three:
想要繼續改進,就得知道問題出在哪裡。很明顯Way two的問題出在,對於一些數你乘了它,又除了它(或它的因子)顯然這些是沒必要的操作.
例如:
C(10,6)=10*9*8*7*6*5/6*5*4*3*2*1
明顯,6和5這兩個數完全不需要乘一遍,所以,至於怎麼實現,就不貼程式碼了.
其次,因為高精度即使壓位了,用運算子*來做依然還是很慢,所以我們可以每次乘一個數再倒過去除一個數,乘的時候從大乘起,除的時候從小除去,這樣一來,效率就可以提上,極限資料為0.7s.
Way four:
我們可以繼續優化,但是我們需要改變一些計算的方法,但本質並沒有變.
因為我們排列組合的時候把許多數都存進去了,但是實際上,如果我們能得到一個所需乘的所有數的因子和所需除的數的所有因子,則我們把這些因子互相減去(可證所乘數的所有因子的任何一個因子的數量絕對不小於所需除的數的因子的數量)最後,剩餘的那些因子,我們再乘起來,再壓一下位,極限資料0.35s左右就可過,幾乎比Way three快一倍。