仍然是數學
卡特蘭數是一個非常神奇的東西
序列長這樣↓
1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786……(從第零項開始)
通常比較常用的應該是遞推式和組合數的求法
遞推式:
f(n)=f(n-1)*(4n-2)/(n+1)
其中令f(0)=1
組合數:
f(n)=C(2n,n)/(n+1)
通常情況下,碰到一道題,你不確定是不是卡特蘭數,可以手動或寫個暴力跑出來比對結果,一般來說前七八項符合條件,那就沒什么問題了,反正我碰到的就是這個樣子2333
我碰到過的卡特蘭數題好像不是很多。吧
【一】二叉樹
求n個節點構成的不同二叉樹個數(n≤8000)
這是一道很經典的題目,很多地方都有,n的范圍可以到七八千左右吧
最朴素的算法就是暴力搜索了,雖然拿不了幾分==
把前面幾個算出來,可以發現就是卡特蘭數的序列,然后就可以直接寫了
發現是卡特蘭數不難,但是關鍵選擇計算卡特蘭數的方法
首先n那么大,顯然是要高精度的,高精度的話復雜度就和答案長度有關了,n為七八千的時候,答案長度會達到四五千,是非常大的一個數,所以必須要考慮壓位
遞推式的話,時間復雜度為O(nlen)也就是遞推復雜度乘上高精度乘除的復雜度
下面放上fp代碼↓
const tt=1000000000; type arr=array[0..550]of int64; var n:longint; ans,ans1:arr; procedure init; var i:longint; begin assign(input,'secret.in');reset(input); assign(output,'secret.out');rewrite(output); readln(n); end; function mul(a:arr;x:longint):arr; var i:longint; c:arr; begin fillchar(c,sizeof(c),0); c[0]:=a[0]; for i:=1 to c[0] do begin inc(c[i],a[i]*x); inc(c[i+1],c[i] div tt); c[i]:=c[i] mod tt; end; if c[c[0]+1]>0 then inc(c[0]); while (c[0]>1)and(c[c[0]]=0) do dec(c[0]); exit(c); end; function divx(a:arr;x:longint):arr; var i:longint; c:arr; yu:int64; begin fillchar(c,sizeof(c),0); c[0]:=a[0];yu:=0; for i:=c[0] downto 1 do begin yu:=a[i] mod x; inc(a[i-1],yu*tt); c[i]:=a[i] div x; end; if c[c[0]+1]>0 then inc(c[0]); while (c[c[0]]=0)and(c[0]>1) do dec(c[0]); exit(c); end; procedure main; var i:longint; begin ans[0]:=1;ans[1]:=1; for i:=2 to n do ans:=divx(mul(ans,4*i-2),i+1); end; procedure print; var i,j:longint; s:string; begin write(ans[ans[0]]); for i:=ans[0]-1 downto 1 do begin str(ans[i],s); for j:=9 downto length(s)+1 do write(0); write(s); end; close(input);close(output); end; begin init; main; print; end.
如果選組合數的話,按照組合數定義一個個跑的話復雜度和遞推差不多==
所以可以進行拆分質因子,因為任何一個合數都可以拆分成若干個質因子相乘的形式,質數看成它本身即↓
X=(p1^a1)*(p2^a2)*(p3^a3)*(p4^a4)*……*(pk^ak) (pi為質數)
所以通過枚舉這些質因子求積,連乘的可以通過快速冪來實現,調用高精度乘法的次數減少了,只用在快速冪和冪的連乘,質因子只有根號個,復雜度就妥妥的降下來了,代碼看下面↓
const tt=1000000000; type arr=array[0..550]of int64; var n:longint; hash:array[0..16005]of longint; vis:array[0..16005]of boolean; ans:arr; procedure init; begin assign(input,'secret.in');reset(input); assign(output,'secret.out');rewrite(output); readln(n); end; procedure maker; var i,j:longint; begin fillchar(vis,sizeof(vis),1); for i:=2 to trunc(sqrt(2*n)) do for j:=2 to 2*n div i do if vis[i] then vis[i*j]:=false; end; function count(x,p:longint):longint; begin count:=0; while x>=p do begin inc(count,x div p); x:=x div p; end; end; function mul(a,b:arr):arr; var i,j:longint; c:arr; begin fillchar(c,sizeof(c),0); c[0]:=a[0]+b[0]-1; for i:=1 to a[0] do for j:=1 to b[0] do begin inc(c[i+j-1],a[i]*b[j]); inc(c[i+j],c[i+j-1] div tt); c[i+j-1]:=c[i+j-1] mod tt; end; if c[c[0]+1]>0 then inc(c[0]); while (c[c[0]]=0)and(c[0]>1) do dec(c[0]); exit(c); end; function power(a,b:longint):arr; var sum,w:arr; begin w[0]:=1;w[1]:=a; sum[0]:=1;sum[1]:=1; while b<>0 do begin if b and 1=1 then sum:=mul(sum,w); w:=mul(w,w); b:=b>>1; end; exit(sum); end; procedure main; var i:longint; begin maker; fillchar(hash,sizeof(hash),0); for i:=2 to n*2 do if vis[i] then hash[i]:=count(n*2,i)-count(n,i)-count(n+1,i); ans[1]:=1;ans[0]:=1; for i:=2 to 2*n do if hash[i]<>0 then ans:=mul(ans,power(i,hash[i])); end; procedure print; var i,j:longint; s:string; begin write(ans[ans[0]]); for i:=ans[0]-1 downto 1 do begin str(ans[i],s); for j:=length(s)+1 to 9 do write(0); write(s); end; close(input);close(output); end; begin init; main; print; end.
所以就解好了
【二】出棧次序
一個棧(無窮大)的進棧序列為1,2,3,…,n,求不同的出棧序列個數
和上面那個題也是一個思路,最后答案序列是卡特蘭數
類似的變形題有買票找零之類的
題目描述長這樣↓
一場激烈的足球賽開始前,售票工作正在緊張的進行中,每張球票為 50 元,現有 2n 個人排隊等待購票,其中有 n 個人手持 50 元的鈔票,另外 n 個人手持 100 元的鈔票,假設開始售票時售票處沒有零錢,問 2n 個人有多少種排隊方式,使售票處不至出現找不開錢的局面。
我最開始做的時候是找規律,發現答案剛好是卡特蘭數就寫了,后來發現可以轉換成進出棧的問題就是50元看成進棧,100元的時候把找回的50元看做一次出棧就一毛一樣了。。
【三】凸多邊形的三角划分
在一個凸多邊形中,通過若干條互不相交的對角線,把這個多邊形划分成了若干個三角形。求n多邊形不同划分的方案數f(n)。
題目變形有在圓上選擇2n個點,將這些點成對連接起來使得所得到的n條線段不相交的方法數之類的
【四】括號化
矩陣連乘: P=A1*A2*A3*……*An,依據乘法結合律,不改變其順序,只用括號表示成對的乘積,求不同括號化的方案數
方案數是f(n-1),然而並沒做過這類的題==
好像就沒有其他什么東西了誒
【寫的有漏洞的,歡迎路過大神吐槽】
2016-08-09 22:35:33
Ending.