母函數(shù)
對(duì)于一般的排列組合算法題, 可首先嘗試通過母函數(shù)來解決。
在數(shù)學(xué)中身笤,某個(gè)序列的母函數(shù)(Generating function结借,又稱生成函數(shù))是一種形式冪級(jí)數(shù),其每一項(xiàng)的系數(shù)可以提供關(guān)于這個(gè)序列的信息鸦泳。使用母函數(shù)解決問題的方法稱為母函數(shù)方法。
母函數(shù)可分為很多種永品,包括普通母函數(shù)做鹰、指數(shù)母函數(shù)、L級(jí)數(shù)鼎姐、貝爾級(jí)數(shù)和狄利克雷級(jí)數(shù)钾麸。對(duì)每個(gè)序列都可以寫出以上每個(gè)類型的一個(gè)母函數(shù)。構(gòu)造母函數(shù)的目的一般是為了解決某個(gè)特定的問題炕桨,因此選用何種母函數(shù)視乎序列本身的特性和問題的類型饭尝。
母函數(shù)的兩個(gè)原則是:
- 把組合問題的加法法則和冪級(jí)數(shù)的乘冪對(duì)應(yīng)起來
- 母函數(shù)的思想就是把離散數(shù)列和冪級(jí)數(shù)一 一對(duì)應(yīng)起來,把離散數(shù)列間的相互結(jié)合關(guān)系對(duì)應(yīng)成為冪級(jí)數(shù)間的運(yùn)算關(guān)系献宫,最后由冪級(jí)數(shù)形式來確定離散數(shù)列的構(gòu)造
普通母函數(shù)
我們先舉一個(gè)簡(jiǎn)單例子, 假設(shè)有1克钥平、2克、3克姊途、4克的砝碼各一 枚涉瘾,請(qǐng)問我們能稱出哪幾種重量?每種重量各有幾種可能方案捷兰?
我們先來看看窮舉的過程立叛,先假設(shè)使用一個(gè)1g的砝碼,然后再假設(shè)在此情況下贡茅,使用2g的砝碼秘蛇,在此基礎(chǔ)上其做,再假設(shè)使用一個(gè)3g的,再假設(shè)使用一個(gè)4g的赁还,這就是第一中情況庶柿,這種方案稱出的重量為10g。
改變上面的一個(gè)假設(shè)條件秽浇,比如假設(shè)沒有使用4g的浮庐,只是用了1g、2g柬焕、3g审残,這種方案稱出的是6g,以此類推斑举,將所有的可能列舉出來后即可得到全部的方案搅轿。
在上述過程中, 每次對(duì)于1g的砝碼都有兩種情況,使用還是不使用富玷,其他的砝碼也一樣璧坟。因此每一種砝碼的狀態(tài)都可以有兩種砝碼狀態(tài),以此類推總共就有2*2*2*2=16中方案赎懦,當(dāng)然其中包含一種一個(gè)砝碼也沒有的0g方案雀鹃。
我們最后可以窮舉出結(jié)果:1g、2g励两、8g黎茎、9g、10g各有一種方案当悔,3g傅瞻、4g、5g盲憎、6g嗅骄、7g各有兩種,總共十五種饼疙,這是在不算一種0g的方案的時(shí)候溺森,算上就是16種,和上面的過程相符宏多。
可以看到挑選不同種類砝碼之間是獨(dú)立的, 也就是說假如現(xiàn)在有1g和2g兩個(gè)砝碼, 所有可能狀態(tài)是, 其中||代表邏輯或, &&代表邏輯與, 每個(gè)用 “||”分開的一項(xiàng)都是一種方案儿惫≡璺#“&&”表示同時(shí)滿足第一項(xiàng)和第二項(xiàng)的條件伸但。:
(使用1g&&使用2g) || (不使用1g&&使用2g) || (使用1g&&不使用2g) || (不使用1g&&不使用2g)
可以看出共有四種狀態(tài), 而這四種狀態(tài)可以概括為:
(使用1g || 不使用1g) && (使用2g || 不使用2g)
上面這個(gè)表達(dá)式與乘法表達(dá)式很像, 那么如果把“||”看成加法,“&&”看成乘法留搔,和多項(xiàng)式的乘法一模一樣更胖。題目需要的是幾種砝碼組合后的重量,是一個(gè)加法關(guān)系,但是在上式中“&&”是一種類似于乘法的運(yùn)算關(guān)系却妨。我們希望找到一種運(yùn)算關(guān)系饵逐,以乘法的形式運(yùn)算,但是結(jié)果表現(xiàn)出類似于加法的關(guān)系彪标。 因此我們自然地想到可以用冪運(yùn)算來表達(dá)倍权。
依然以1g和2g兩個(gè)砝碼進(jìn)行說明, 假如用, 表示1g砝碼, 表示2g砝碼, 注意冪指數(shù)代表是幾g砝碼。
那么之前的 (使用1g || 不使用1g) && (使用2g || 不使用2g) 這個(gè)條件就可以通過如下運(yùn)算式表示(我們之前提到過&&相當(dāng)于, ||相當(dāng)于+):
注意上式中的代表不使用當(dāng)前砝碼, 因?yàn)槭褂?g砝碼就相當(dāng)于不使用砝碼捞烟”∩可以理解為, 本來在該放1g,2g,3g砝碼的地方放入了一個(gè) 0g 砝碼作為替換。所以就有:
很顯然题画,有四種方案默辨,0g、1g苍息、2g缩幸、3g,結(jié)果與我們窮舉的結(jié)果相同竞思,而如果結(jié)果中有相同的項(xiàng)表谊,那么合并同類項(xiàng)后每一項(xiàng)的系數(shù)就是這種重量有幾種實(shí)現(xiàn)方案。我們現(xiàn)在用這種方法表示 1g,2g,3g,4g 四種砝碼的所有情況盖喷。
其中代表由四種砝碼組合成3g砝碼共有兩種情況, 1g+2g和3g本身铃肯。注意前面的系數(shù)2代表組成當(dāng)前砝碼質(zhì)量3共有2種不同組合方式。
接著我們把問題改一下, 求用1g传蹈、2g押逼、3g的砝碼稱出總重量小于10g的不同重量的方案數(shù)。這題乍看上去和之前沒區(qū)別, 不過需要注意的是, 此處砝碼是無(wú)限的惦界。那么我們?nèi)绾斡媚负瘮?shù)表達(dá)出 無(wú)限 這種性質(zhì)呢?
我們以2g砝碼為例挑格,因?yàn)槲覀冇袩o(wú)限個(gè)2g砝碼,所以我們可以把2個(gè)2g砝碼組合成一個(gè)4g砝碼沾歪,3個(gè)2g砝碼組合成一個(gè)6g砝碼漂彤,依次類推,我們光靠2g砝碼, 就可以擴(kuò)充我們已有的砝碼種類為 1g,2g,3g,4g,6g,8g,10g灾搏。如果我們按照這種方法接著對(duì)1g和3g砝碼做擴(kuò)充, 那么我們能得到從1g到10g的所有砝碼種類挫望。為何最大是10呢, 因?yàn)槲覀兇颂幰?guī)定了所有砝碼組合最大不超過10g, 那么超過10g的砝碼也就沒了意義, 所以我們只需要最大為10g的砝碼種類。
那么之前的公式就可以變?yōu)?():
\begin{align}
&\qquad(1g砝碼的所有組合)\cdot(2g砝碼的所有組合)\cdot(3g砝碼的所有組合)\
&\
&=(x^0 +x^1 + x^2 + x^3 +\cdots+x{10})\cdot(x0+x2+x4+x6+x8+x{10})\cdot(x0+x3+x6+x^9)\
\end{align}
將括號(hào)打開后所得的多項(xiàng)式的系數(shù)就是不同重量的組合方式狂窑。
現(xiàn)在我們來考慮一下情況下的通項(xiàng)公式:
其中(注意, 上邊是多項(xiàng)式的通項(xiàng)公式, 跟我們之前舉的例子沒有關(guān)系):
K對(duì)應(yīng)具體問題中物品的種類數(shù)媳板。
v[i]表示該乘積表達(dá)式第i個(gè)因子的權(quán)重,對(duì)應(yīng)于上訴問題為每個(gè)砝碼的的重量泉哈。
表示該乘積表達(dá)式第i個(gè)因子的起始系數(shù)蛉幸,對(duì)應(yīng)于具體問題中的每個(gè)物品的最少個(gè)數(shù)破讨,即最少要取多少個(gè)。
表示該乘積表達(dá)式第i個(gè)因子的終止系數(shù)奕纫,對(duì)應(yīng)于具體問題中的每個(gè)物品的最多個(gè)數(shù)提陶,即最多要取多少個(gè)。
假如我們現(xiàn)在題目是:給2張1元匹层,5張2元隙笆,3張5元,要得到15元升筏,有多少種組合仲器?
根據(jù)上述問題, 我們可以得到表達(dá)式:
我們注意到第一個(gè)多項(xiàng)式, 其代表是有1元的所有組合的情況, 即為(0張1元 || 1張1元 || 2張1元)。因?yàn)槲覀兿喈?dāng)于是從0張1元開始, 到2張1元停止仰冠。所以我們此時(shí)有乏冀。
以此類推, 代表由2元組成的表達(dá)式, 故可表示 (0張2元 || 1張2元 || 2張2元 || 3張2元 || 4張2元 || 5張2元 ), 故此時(shí)有
第三個(gè)表達(dá)式是由5元組成, 故
總結(jié)一下, ={1,2,5}, ={0,0,0}, ={2,5,4}。一般情況下都為0, 但也有特殊情況洋只。
已知Fib(n)=Fib(n-1)+Fib(n-2), 這里假設(shè)Fib(1)=1,Fib(2)=1辆沦。因?yàn)镕ib(1)和Fib(2)都為1, 也就是說一次項(xiàng)和二次項(xiàng)的系數(shù)都為1, 那么根據(jù)如下三個(gè)求遞推公式的原則:
1.將遞推關(guān)系變成母函數(shù)方程
2.求解母函數(shù)方程
3.將母函數(shù)變成冪級(jí)數(shù)形式
我們首先寫出斐波那契數(shù)列的生成函數(shù)
等式兩邊同時(shí)乘, 有:
兩式相加, 有:
我們對(duì)比, 可得:
所以我們可以得到:
可以令: , 得到兩根為: , , 所以我們可以知道:
假設(shè), 通分有: 。由系數(shù)關(guān)系可得, 所以
我們可知:
是以公比為的等比數(shù)列识虚,是以公比為的等比數(shù)列肢扯,所以其通項(xiàng)公式為:
若有1克、2克担锤、3克蔚晨、4克的砝碼各一 枚,能稱出哪幾種重量肛循?各有幾種可能方案铭腕?
構(gòu)造母函數(shù),如果用x的指數(shù)表示稱出的重量多糠,則:
1個(gè)1克的砝碼可以用函數(shù)表示累舷,(前面的這個(gè)1表示1克的砝碼個(gè)數(shù)為0, 即)
1個(gè)2克的砝碼可以用函數(shù)表示,
1個(gè)3克的砝碼可以用函數(shù)表示夹孔,
1個(gè)4克的砝碼可以用函數(shù)表示被盈,
那么幾種砝碼的組合情況的用乘積表示有:
其中系數(shù)即為方案數(shù),譬如乘出重量為6的物品共有2種方案搭伤。
求用1分只怎、2分、3分的郵票貼出不同數(shù)值的方案數(shù)怜俐?
這個(gè)相對(duì)于上面的那個(gè)例子是:這個(gè)郵票可以重復(fù)身堡。可知其生成函數(shù)為:
重為 的砝碼, 如何放在天平的兩端, 記可稱重量為n的物體的不同方式為, 則的母函數(shù)為:
其中表示砝碼a1和物體放在同一個(gè)托盤內(nèi), 表示砝碼和物體放在不同的托盤內(nèi), 則為用一個(gè)0g的砝碼代替, 即不用1g這個(gè)砝碼的意思佑菩。
重為 的砝碼, 若只可以放在天平的一端, 記可稱重量為n的物體的不同方式為, 則的母函數(shù)為:
數(shù)的劃分盾沫,將整數(shù)分解為若干個(gè)整數(shù)(相當(dāng)于將n個(gè)蘋果放在n個(gè)無(wú)區(qū)別的盤子里裁赠,每個(gè)盤子可以放多個(gè)殿漠,也可以不放)
假設(shè)1出現(xiàn)的次數(shù)為記為, 2出現(xiàn)的次數(shù)記為 赴精。k出現(xiàn)的次數(shù)記為,那么生成函數(shù)為:
前面的意思是當(dāng)出現(xiàn)一個(gè)2時(shí)為,當(dāng)出現(xiàn)兩個(gè)2時(shí)為
為什么當(dāng)出現(xiàn)n時(shí)绞幌,只有兩項(xiàng), 因?yàn)槭菍?shù)n劃分為若干項(xiàng)蕾哟,所以不能超過該數(shù),且由數(shù)1到n項(xiàng)數(shù)依次要
#include <iostream>
#include<vector>
using namespace std;
int specificChoice(int query_value,const vector<pair<int,int> > &choices){ //接受兩個(gè)參數(shù), 一個(gè)是要查詢組合種類的數(shù)值, 一個(gè)是一個(gè)vector, vector中每個(gè)pair代表<砝碼重量, 該重量砝碼的數(shù)量>, <2,4>代表2g的砝碼有4個(gè)
int maximum=0;
for(auto &t:choices){
maximum+=t.first*t.second; //此處求出最高的冪次項(xiàng), 作為下面循環(huán)的停止條件莲蜘。
}
/*
為了計(jì)算多項(xiàng)式, 我們首先要初始化一個(gè)vector用來存多項(xiàng)式的每個(gè)項(xiàng), 該vector的下標(biāo)指代的是冪, 值為該冪的項(xiàng)所對(duì)應(yīng)的系數(shù)谭确。即cumulative_coeff[2]=5代表x^2項(xiàng)的系數(shù)為5, 即5x^2
*/
vector<int> cumulative_coeff(maximum+1,0); //注意此處maximum也要取到, 所以初始化的大小要加一
vector<int> temp(maximum+1,0);
cumulative_coeff[0]=1; //我們此處需要把砝碼重量為0的值初始化為1, 因?yàn)楹髞淼夹枰獜脑撎庨_始, 后續(xù)的計(jì)算方式都是以此作為基礎(chǔ)
/*
假設(shè)我們有1g,2g,3g砝碼各一個(gè), 故有多項(xiàng)式為(1+x)(1+x^2)(1+x^3), 我們首先說明, 作為最外層循環(huán), i是用來以每個(gè)多項(xiàng)式為基本單位來遍歷的, 即(1+x),(1+x^2)和(1+x^3)這三個(gè)多項(xiàng)式, 故i遍歷三次。實(shí)際上i的遍歷次數(shù)取決于我們傳入的物品種類數(shù)目
既然i指代的每個(gè)具體的表達(dá)式, j相當(dāng)于是遍歷上一輪得到的cumulative_coeff的結(jié)果的, 然后在j的循環(huán)中, 還有一個(gè)k的循環(huán), k是用來遍歷當(dāng)前多項(xiàng)式中每個(gè)元素的票渠。
因?yàn)?1+x)(1+x^2)(1+x^3)最終最高次冪是6, 所以在經(jīng)過初始化后, 我們的cumulative_coeff列表為:
1 0 0 0 0 0 0
我們開始第一輪迭代, 當(dāng)i=0時(shí), 我們遍歷第一個(gè)多項(xiàng)式(1+x), 然后我們現(xiàn)在需要把這個(gè)(1+x)累加到cumulative_coeff中, 那么我們就對(duì)cumulative_coeff整個(gè)用j遍歷一遍逐哈。在每一輪遍歷j的過程中, 我們用k來遍歷當(dāng)前(1+x)這個(gè)多項(xiàng)式, 并將其累加到j(luò)遍歷到的地方
當(dāng)j=0時(shí), 相當(dāng)于選擇cumulative_coeff[0],
當(dāng)k=0時(shí), 即代表選擇了(1+x)中的1, temp[k+j]+=(cumulative_coeff[0]=1)
當(dāng)k=choices[i].first=1時(shí), 即代表選擇了(1+x)中的x, temp[k+j]+=temp[0+1]=(cumulative_coeff[0]=1) , 這就是用temp的意義, 不會(huì)重復(fù)+=, 否則如果直接在cumulative_coeff上做+=的話每次就重復(fù)計(jì)算了
當(dāng)j=1時(shí), 相當(dāng)于選擇cumulative_coeff[1],
當(dāng)k=0時(shí), 即代表選擇了(1+x)中的1, temp[1]+=(cumulative_coeff[1]=0) 在上一輪j=0中temp[1]已經(jīng)被加為1了, 在此輪加0
當(dāng)k=choices[i].first=1時(shí), 即代表選擇了(1+x)中的x, temp[2]=cumulative_coeff[1]=0
當(dāng)j=2,3,4,5,6時(shí), 因?yàn)閏umulative_coeff[j]=0, 所以對(duì)應(yīng)的temp[j+k]+=0
......
在遍歷完所有的j后, 將temp上的值賦給cumulative_coeff, 注意不是+=而是=, 然后將temp所有元素重新初始化為0
此時(shí)cumulative_coeff為:
1 1 0 0 0 0 0
開始第二輪迭代, 當(dāng)i=1時(shí), 我們遍歷第而個(gè)多項(xiàng)式(1+x^2), 然后我們現(xiàn)在需要把這個(gè)(1+x^2)累加到上一輪的cumulative_coeff中, 注意此處是1g,2g,3g砝碼各一個(gè), 如果不止一個(gè)的話k的最大值就不止是1:
當(dāng)j=0時(shí), 相當(dāng)于選擇cumulative_coeff[0],
當(dāng)k=0時(shí), 即代表選擇了(1+x^2)中的1, temp[k+j]+=1 注意temp在上一輪結(jié)束的時(shí)候已經(jīng)全部初始化為0, 所以此處結(jié)果是temp[0]=1
當(dāng)k=choices[i].first=2時(shí), 即代表選擇了(1+x^2)中的x^2, temp[k+j]+=(cumulative_coeff[0]=1) , 故temp[2]=1
......
*/
for(int i=0;i<choices.size();i++){
for(int j=0;j<=maximum;j++){
for(int k=0;k+j<=maximum && k/choices[i].first<=choices[i].second;k+=choices[i].first){
temp[k+j]+=cumulative_coeff[j];
}
}
for(int j=0;j<=maximum;j++){
cumulative_coeff[j]=temp[j];
temp[j]=0;
}
cout<<"After finish<"<<choices[i].first<<","<<choices[i].second<<">: ";
for(int co=0;co<=maximum;co++){ //每一輪結(jié)束后輸出本輪累加的cumulative_coeff, 便于理解
cout<<cumulative_coeff[co]<<" ";
}
cout<<endl;
}
/*
如果要求的問題是, 求最小的不能由這些砝碼組成的重量是多少, 只需要把return替換為:
for(i=0;i<=maximum;i++{
if(value[i]==0){
return i;
}
}
return -1;
*/
return cumulative_coeff[query_value];
}
int main()
{
vector<pair<int,int> > inputs;
//給定3個(gè)2g的砝碼, 4個(gè)3g的砝碼, 2個(gè)5g的砝碼
inputs.push_back({2,3});
inputs.push_back({3,4});
inputs.push_back({5,2});
//求解用這些砝碼得到總重量為28g的所有可能組合數(shù)量
cout<<specificChoice(28,inputs)<<endl;
return 0;
}
將正整數(shù)n表示成一系列正整數(shù)之和: , 其中。
正整數(shù)的這種表示稱為正整數(shù)的劃分问顷。求正整數(shù)n的不同劃分個(gè)數(shù)昂秃。
例如正整數(shù)6有如下11種不同的劃分:
6
5+1
4+2 4+1+1;
3+3 3+2+1 3+1+1+1;
2+2+2 2+2+1+1 2+1+1+1+1;
1+1+1+1+1+1
現(xiàn)在就是無(wú)限砝碼問題了, 設(shè)置一個(gè)最大上界為正整數(shù)n, 然后只要小于等于n的數(shù), 其每個(gè)數(shù)都可以被選用無(wú)數(shù)次。假設(shè)正整數(shù)n為6, 則我們有1,2,3,4,5,6這6個(gè)砝碼, 每個(gè)都有無(wú)限的數(shù)量, 僅僅要求不同砝碼組合的總重量小于整數(shù)n杜窄。
那么我們可以對(duì)上面的稍加變形
int infiniteChoice(int NUM){
vector<int> cumulative_coeff(NUM+1,0);
vector<int> temp(NUM+1,0);
cumulative_coeff[0]=1;
/*
注意i要從1開始加起, 如果i是0的話, 底下的k+=i就會(huì)一直是k+=0, 就會(huì)陷入死循環(huán), 而且因?yàn)槲覀冎粚?duì)0g砝碼做了初始化, 所以本來也要從1g砝碼開始考慮肠骆。
之所以上面那個(gè)代碼i是從0開始加起, 是因?yàn)樵谘h(huán)中, k+=choices[i].first 。choices[i].first一般來說不為0塞耕。
總體來說我們所要做的改動(dòng)就是, i的上界變?yōu)榱薔UM, 因?yàn)橹灰∮贜UM的所有砝碼都是有無(wú)限個(gè)的, 所以產(chǎn)生的多項(xiàng)式是:
(1+x+x^2+x^3+...)(1+x^2+x^4...)(1+x^3+x^6...)......(1+x^n)
第二個(gè)改動(dòng)是, k不再被砝碼的數(shù)量所限制了, 每一輪循環(huán)的限制條件變?yōu)榱藘H剩 k+j<=NUM蚀腿。而且k每一輪也從 k+=choices[i].first變?yōu)榱薻+=1, 因?yàn)楝F(xiàn)在i就指代砝碼重量本身, 畢竟現(xiàn)在的砝碼重量種類是從1到n一個(gè)連續(xù)的序列。
其他就沒什么改動(dòng)了
*/
for(int i=1;i<=NUM;i++)
{
for(int j=0; j<=NUM; j++){
for(int k=0; k+j<=NUM; k+=i)
{
temp[j+k] += cumulative_coeff[j];
}
}
for(int j=0; j<=NUM; ++j){
cumulative_coeff[j] = temp[j];
temp[j] = 0;
}
}
return cumulative_coeff[NUM];
}
卡特蘭數(shù)
我們首先來看一個(gè)例子, 假如現(xiàn)在有 n 個(gè)+1 和 n個(gè)-1, 總計(jì)2n個(gè)數(shù), 要用其組成一個(gè)序列, 要求在序列中任何一點(diǎn)k上, 都有, 其中扫外。換句話說, 即:
序列1: 1 1 -1 -1 1 ...
序列2: 1 -1 1 -1 -1 ...
序列1看上去是暫時(shí)沒錯(cuò)的, 因?yàn)闊o(wú)論在哪個(gè)位置, 該位置之前(包括該元素)的元素和都大于等于0, 而序列2不是, 所以序列二不滿足要求
我們滿足上述要求的稱為卡特蘭數(shù), 假設(shè)我們?cè)O(shè)不滿足上述要求的序列的數(shù)量為, 那么就等于n 個(gè)+1 和 n個(gè)-1 的的數(shù)量莉钙。
這個(gè)所有排列組合的數(shù)量如果用排列組合來表示即為 , 怎么理解這個(gè)呢?
我們可以理解為, 假設(shè)現(xiàn)在有一個(gè)長(zhǎng)度為2n的空的插槽, 我們從這2n個(gè)插槽中選出n個(gè)插槽, 插入n個(gè)+1, 那么剩下的插槽自然都是-1了。故為筛谚。注意此處的與卡特蘭數(shù)的表示沒有什么關(guān)系, 只是都用來表示而已胆胰。
那我們現(xiàn)在有了 , 下一步就是要求了。
我們假設(shè)有一個(gè)最小的k令, 由于此處k是最小的, 所以必有, 并且k是一個(gè)奇數(shù)刻获。為了幫助理解, 我們假設(shè)現(xiàn)在有如下這個(gè)不滿足要求的序列(n=7):
1 -1 1 1 -1 -1 1 -1 -1 1 1 1 -1 -1
那么這個(gè)最小的k等于多少呢, 等于9(下標(biāo)從1開始算), 即在之前, 序列為:
1 -1 1 1 -1 -1 1 -1
其和為剛好為0, 加上后, 即加上-1, 其和即為-1了, 而且此時(shí)也是奇數(shù)蜀涨。此時(shí)我們將前項(xiàng)(包括第k項(xiàng))的-1變?yōu)?, 1變?yōu)?1, k后面的序列不變, 那么上述序列變?yōu)?
-1 1 -1 -1 1 1 -1 1 1 1 1 -1 -1
很顯然, 此時(shí)整個(gè)序列中有8個(gè)+1, 有6個(gè)-1。(因?yàn)榘言瓉硎?1的n變?yōu)榱?1, 所以+1數(shù)量加一, -1數(shù)量減一)蝎毡。而最初的序列有7個(gè)+1和7個(gè)-1, 故當(dāng)時(shí)n=7厚柳。所以在改變后, 現(xiàn)在有n+1個(gè)+1, 有n-1個(gè)-1。
擴(kuò)展來說, 所有不滿足卡特蘭數(shù)條件的序列, 其經(jīng)過翻轉(zhuǎn)前k項(xiàng)后都會(huì)導(dǎo)致整個(gè)2n序列變?yōu)榘琻+1個(gè)+1和n-1個(gè)1, 那么換個(gè)角度來看, 是否只要滿足一個(gè)2n序列, 只要其包含n+1個(gè)+1和n-1個(gè)-1, 其所有的排列組合就等價(jià)于所有不滿足卡特蘭數(shù)序列的2n序列沐兵。換句話說, 包含n+1個(gè)+1和n-1個(gè)-1的序列的任何排列組合方式, 都可以將其按上述的方式反向翻轉(zhuǎn), 得到一個(gè)最小k元素, 滿足别垮。那么這么一來, 不滿足卡特蘭數(shù)列條件的2n序列就一個(gè)包含n+1個(gè)+1和n-1個(gè)-1的2n序列。如果覺得此處證明不嚴(yán)謹(jǐn)請(qǐng)查閱編程之美4.3扎谎。
那么很顯然, 其就是我們要找的序列, 故, 即相當(dāng)于從2n個(gè)插槽中選出n+1個(gè)插入+1或是n-1個(gè)插入-1, 注意其中 碳想。
因此我們就有 (其中) :
這就是我們要求的卡特蘭數(shù)的公式, 根據(jù)上式可得其遞推公式為:
同時(shí), 其還有如下性質(zhì)(注意等式左邊是而右邊的項(xiàng)是):
以及:
而其增長(zhǎng)趨勢(shì), 即復(fù)雜度為:
我們可以把+1看為入棧, 而-1看做是出棧, 不能對(duì)一個(gè)空棧做出棧操作, 也就是說在每次出棧操作時(shí), 要確保棧中元素大于0烧董。換句話說, 也就是說對(duì)于序列中任意一個(gè)元素, 都有。假設(shè)我們有n個(gè)數(shù), 其入棧和出棧的排列總數(shù)為, 例如1,2,3入棧的出棧排序有123胧奔,132逊移,213,231和321五種龙填。
如上文所說胳泉,對(duì)于任意的k,前k個(gè)元素中-1的個(gè)數(shù)小等于+1的個(gè)數(shù)的序列計(jì)數(shù)岩遗,我們可以不停地變換形式扇商,比如將-1看成右括號(hào),+1看成左括號(hào)宿礁,就變成了合法括號(hào)表達(dá)式的個(gè)數(shù)案铺。比如2個(gè)左括號(hào)和2個(gè)右括號(hào)組成的合法表達(dá)式有 種, 是 ()() 和 (()) 。也就是說')'不能先于'('出現(xiàn), 即')'出現(xiàn)時(shí)前面一定要有一個(gè)'('與其匹配梆靖。
既然如上一點(diǎn)都把括號(hào)加上去了控汉,那么順便就再次轉(zhuǎn)換,n+1個(gè)數(shù)連乘涤姊,乘法順序有種暇番。或者更常見的, 三個(gè)矩陣連乘, 不過因?yàn)榫仃嚪铣朔ńY(jié)合率, 所以跟三數(shù)連乘沒什么區(qū)別思喊。
因?yàn)楸热缥覀內(nèi)齻€(gè)數(shù)連乘abc壁酬,那么等于在式子上加括號(hào),有2種乘法順序恨课,分別是和舆乔。
我們?cè)偃為3來看看, 注意此處n指的是'('或')'單括號(hào)的數(shù)量,n為3的時(shí)候就是4個(gè)數(shù)相乘了剂公,那么我們?cè)O(shè)為abcd希俩,最初的標(biāo)號(hào)定在a上,我們對(duì)于n為3得到合法的括號(hào)序列有5個(gè)纲辽,分別是:((()))颜武,()(()),()()()拖吼,(())()和(()())鳞上,那么我們將一個(gè)左括號(hào)看成是當(dāng)前操作數(shù)指針往右移動(dòng)一個(gè)位置,一個(gè)右括號(hào)看成是當(dāng)前操作數(shù)和左邊最近的一塊操作數(shù)相乘起來吊档,那么對(duì)應(yīng)的五個(gè)表達(dá)式就是:a(b(cd))篙议,(ab)(cd),((ab)c)d,(a(bc))d和a((bc)d)鬼贱,他們之間是一一對(duì)應(yīng)關(guān)系移怯。
n個(gè)節(jié)點(diǎn)的二叉搜索樹的所有可能形態(tài)數(shù)為。注意是二叉搜索樹, 即左子節(jié)點(diǎn)比父節(jié)點(diǎn)小, 右子節(jié)點(diǎn)比父節(jié)點(diǎn)大这难。
假設(shè)我們現(xiàn)在有一個(gè)有序序列[1,2,3,4,5,6,7,8], 問總共能組成多少種不同的二叉搜索樹舟误。為了能得到所有可能二叉樹的情況, 我們需要遍歷這個(gè)序列, 將序列中每個(gè)元素分別當(dāng)做一次根節(jié)點(diǎn)。
假設(shè)我們現(xiàn)在選擇了下標(biāo)為2的元素3(下標(biāo)從0開始)作為根節(jié)點(diǎn), 那么此時(shí)該根節(jié)點(diǎn)的左子樹和右子樹中包含的節(jié)點(diǎn)是不是確定的? 注意是二叉搜索樹, 所以{1,2}落在了左子樹, {4,5,6,7,8}落在了右子樹雁佳。
而且此時(shí)左子樹和右子樹的排列方式是相互獨(dú)立的, {1,2}有2中方式組成一個(gè)二叉搜索樹, {4,5,6,7,8}有42種方式(先不用管這個(gè)是怎么得到的)組成一個(gè)二叉搜索樹年柠。那么, 此時(shí)當(dāng)以3作為根節(jié)點(diǎn)時(shí), 所有子樹的可能情況總和就是
再次重申, 左子樹和右子樹是相互獨(dú)立的, 所以兩者的排列組合相當(dāng)于是笛卡爾積, 所以是2*42=84相乘奖磁。
我們?cè)賮硭伎家幌? 當(dāng)以3作為根節(jié)點(diǎn)將序列分為{1,2}和{4,5,6,7,8}以及當(dāng)以6作為根節(jié)點(diǎn)將序列分為{1,2,3,4,5}和{7,8}時(shí), 其結(jié)果是否是一樣的呢? 其實(shí)兩者最后的結(jié)果是一樣的, 都是84堂鲜。
那么, 這說明了, 當(dāng)一個(gè)根節(jié)點(diǎn)把一個(gè)有序數(shù)列(注意有序這個(gè)條件)分成左右兩個(gè)子樹時(shí), 分到子樹中的元素的值不重要, 即{1,2}和{7,8}兩者沒什么區(qū)別, 重要的是分到子樹中的元素的數(shù)量拯腮。即只要分到左子樹或右子樹中2個(gè)元素, 那么無(wú)論這兩個(gè)元素是{1,2},{3,4}還是{5,6}, 其最終該子樹的排列組合數(shù)量都是2篱蝇。
那么[1,2,3,4,5,6,7,8]這個(gè)序列可能產(chǎn)生的所有可能BST(二叉搜索樹)的數(shù)量就等于令序列中每一個(gè)元素為根節(jié)點(diǎn)時(shí), 左子樹乘右子樹得到的笛卡爾積, 即像2*42=84那樣凳枝。
我們把這個(gè)過程抽象一下, 假設(shè)我們現(xiàn)在用第i個(gè)元素將序列分成左右兩個(gè)序列, 該左右兩個(gè)序列是不是又可以分別被當(dāng)成新的序列, 遞歸地再分別被分成兩半沪铭。而且就像我們上面證明的, 分成兩半時(shí)每個(gè)序列中元素本身值不重要, 序列中每個(gè)元素的數(shù)量才重要, 那么我們就可以得出如下遞推公式:
其中代表該序列中包含i個(gè)元素, 代表其包含n-1-i個(gè)元素, 注意因?yàn)槊恳惠喰枰獙?dāng)前作為根節(jié)點(diǎn)的節(jié)點(diǎn)排除在外, 所以此處是n-1-i髓迎。 假如序列長(zhǎng)度為n=8, i=2時(shí), 即相當(dāng)于用從左往右數(shù)第3個(gè)元素(i是從下標(biāo)0開始的)作為根節(jié)點(diǎn), 那么此處左子樹就有i=2個(gè)節(jié)點(diǎn), 右子樹就有8-1-2=5個(gè)節(jié)點(diǎn)旱易。注意此處有個(gè)trick, 因?yàn)槲覀冚斎氲膎=8, 所以當(dāng)我們用n-1的時(shí)候就自動(dòng)將當(dāng)前根節(jié)點(diǎn)排除在外了禁偎。 即n-1-i跟的上界是n-1沒直接關(guān)系, 的上界是用來從0到n-1遍歷這n個(gè)元素, 分別用其作為根節(jié)點(diǎn)。
那么和怎么求呢, 此時(shí)需要將分到左子樹和右子樹的序列分別當(dāng)做新的輸入序列, 重新開始遞歸求解阀坏。
我們看上述遞推公式, 再與卡特蘭數(shù)的遞推公式對(duì)比一下:
會(huì)發(fā)現(xiàn)唯一的區(qū)別就是用n-1替換為了n, 也就是說, 其就是卡特蘭數(shù), 那么此時(shí)我們可以直接使用卡特蘭數(shù)的公式求解如暖。
即:
long C=1;
for(int i=0;i<n;i++){
C = C * 2 * (2 * i + 1) / (i + 2);
}
其中n代表序列的長(zhǎng)度。
對(duì)于一個(gè)n*n的正方形網(wǎng)格忌堂,每次我們能向右或者向上移動(dòng)一格盒至,而且不能越過對(duì)角線。那么從左下角到右上角的所有在副對(duì)角線右下方的路徑總數(shù)為士修。
我們將一條水平向右走記為+1, 向上走記為-1, 那么就組成了一個(gè)n個(gè)+1和n個(gè)-1的序列, 我們所要保證的就是前k步中向右走的個(gè)數(shù)不小于向上走的次數(shù), 換句話說前k個(gè)元素的和非負(fù), 就是我們關(guān)于Catalan數(shù)的定義枷遂。
給定一個(gè)n邊形, 要求用n-3條不相交的對(duì)角線將其分為n-2個(gè)三角形, 求共有多少種分法。下圖是六邊形, 其總共有14種分法棋嘲。
還記得我們?cè)诙嫠阉鳂淠遣糠痔岬降淖笥易訕溥f歸分割嗎, 此處也是相同的道理酒唉。假設(shè)n邊型共有n個(gè)頂點(diǎn), 依次將其編號(hào)為 。從中任選一個(gè)頂點(diǎn), 連接和沸移。
如上圖, 那么我們這條線段將多邊形分成了兩部分, 一部分是從到共k個(gè)點(diǎn), 故為k邊形痪伦。而這個(gè)k邊形又可以當(dāng)做全新的多邊形, 遞歸地進(jìn)行下一次分割。
另一部分是從共n-k+1個(gè)點(diǎn)(因?yàn)?img class="math-inline" src="https://math.jianshu.com/math?formula=v_k" alt="v_k" mathimg="1">和點(diǎn)本身也包含在內(nèi)故要+1)雹锣。
那么此時(shí)我們就求得了遞推公式, 即:
那么, 我們下一步就該求的上界了, 的上界就是在遞歸的一開始, 我們最多能選多少個(gè)點(diǎn)作為和的連線? 換句話說也就是求k的取值問題网沾。其實(shí)的取值范圍是{}共n-2個(gè)選擇。
為什么是到而不是到呢笆制?注意看上圖, 和是不是連在一起, 那也就是說我們沒法用這條線斷將多邊形分為兩部分绅这。所以此時(shí)的通項(xiàng)公式可以寫為:
注意, n邊形的n要大于等于3。
寫為代碼為:
C[2]=C[3]=1;
for(int i=4;i<Max_n;i++)
{
C[i]=0;
for(int j=2;j<=i-1;j++)
C[i]+=C[j]*C[i-j+1];
}
那么我們此時(shí)就想, 能不能把這個(gè)通項(xiàng)公式簡(jiǎn)化一些呢?
既然討論所謂的“1邊型”在辆、“2邊型”其實(shí)根本沒有意義证薇,“3邊型”及以上才有意義度苔,而且3是n的起始值,那么不妨令表示n+2邊型的劃分?jǐn)?shù)浑度,這樣就從開始寇窑。換句話說, 我們此時(shí)需要改變一下設(shè)定n的方式, 之前是把n設(shè)定為n邊形邊的數(shù)量。現(xiàn)在加入給定的是六邊形, 我們就將n設(shè)定為6-2=4箩张。以此類推, n代表求的是n+2邊形, 此時(shí)我們既可以得到如下遞推公式:
對(duì)于集合的不交叉劃分的數(shù)目為甩骏,這里解釋一下不交叉劃分。
我們對(duì)于集合{a,b}和{c,d}先慷,假設(shè)他們組成了兩個(gè)區(qū)間[a,b]和[c,d]饮笛,我們假設(shè)兩個(gè)區(qū)間不重合,那么以下四種情況當(dāng)做是不交叉的:a<c<d<b论熙,a<b<c<d福青,c<a<b<d與c<d<a<b,就是說兩個(gè)區(qū)間可以包含或者相離脓诡,那么此時(shí)我們稱集合{a,b}和{c,d}是不交叉的无午。
對(duì)于集合,將里面元素兩兩分為一子集祝谚,共n個(gè)宪迟,若任意兩個(gè)子集都是不交叉的,那么我們稱此時(shí)的這個(gè)劃分為一個(gè)不交叉劃分交惯。此時(shí)不交叉的劃分?jǐn)?shù)就是我們的了次泽,證明也很容易,我們將每個(gè)子集中較小的數(shù)用左括號(hào)代替商玫,較大的用右括號(hào)代替箕憾,那么帶入原來的1至2n的序列中就形成了合法括號(hào)問題,就是我們第二點(diǎn)的結(jié)論拳昌。例如我們的集合{1,2,3,4,5,6}的不交叉劃分有五個(gè):{{1,2},{3,4},{5,6}}袭异,{{1,2},{3,6},{4,5}},{{1,4},{2,3},{5,6}}炬藤,{{1,6},{2,3},{4,5}}和{{1,6},{2,5},{3,4}}御铃。
n層的階梯切割為n個(gè)矩形的切法數(shù)也是, 如下圖所示(下圖為n=4的情況):
我們先繪制如下的一張圖片,即n為5的時(shí)候的階梯:
這個(gè)證明是怎么進(jìn)行的呢沈矿?首先我們令n等于5, 即5層階梯, 我們注意到每個(gè)切割出來的矩形都必需包括一塊標(biāo)示為*的小正方形上真,那么我們此時(shí)將每個(gè)*與#標(biāo)示的兩角作為矩形, 將其取出, 作為一種情況, 那么取出矩形后的階梯就剩下兩個(gè)小階梯, 這兩個(gè)小階梯就是兩個(gè)更小的子問題了。 我們可以聯(lián)想二叉搜索樹那部分, 每次選擇一個(gè)節(jié)點(diǎn), 將序列分成左右兩半, 此處是每次選擇一個(gè)以*和#作為對(duì)角的矩形, 將整個(gè)階梯分為兩半羹膳。
那么具體的分法看下圖, 每種不同顏色的框代表是一種取出矩形的方法, 注意觀察取出框內(nèi)元素時(shí), 階梯內(nèi)剩余的元素被分為了兩個(gè)小階梯睡互。
因此我們有
很顯然這就是一個(gè)卡特蘭數(shù)。故其滿足通項(xiàng)公式
假如有2n個(gè)人, 其身高都各不相同, 要求現(xiàn)在將這2n個(gè)人排成兩排, 要求: (1)每人比左邊的人高, 比右邊的人矮 (2)在同一列中的兩個(gè)人, 后面的比前面的高。
這道題咋看上去毫無(wú)頭緒, 但是抽象一下, 就是: 在一個(gè)2行n列的格子中填入1到2n這些數(shù)值使得每個(gè)格子內(nèi)的數(shù)值都比其右邊和下邊的所有數(shù)值都小的情況數(shù)就珠。
假設(shè)我們有8個(gè)數(shù), 即 1 2 3 4 5 6 7 8, 即此時(shí)n=4, 然后我們令這8個(gè)數(shù)從小到大排列, 現(xiàn)在我們從中隨機(jī)選4個(gè)數(shù), 放到第一行中, 剩下4個(gè)數(shù)放到第二行中, 假設(shè)我們的排列順序是:
1 2 3 4
5 6 7 8
其中1 2 3 4四個(gè)數(shù)在第一行, 5 6 7 8四個(gè)數(shù)在第二行, 假設(shè)在第一行設(shè)為0, 在第二行設(shè)為1, 那么這8個(gè)數(shù)所在隊(duì)列就可以表示為
1 2 3 4 5 6 7 8
0 0 0 0 1 1 1 1
如果我們的排列順序是:
1 2 5 7
3 4 6 8
其中1 2 5 7四個(gè)數(shù)在第一行, 3 4 6 8四個(gè)數(shù)在第二行, 那么這8個(gè)數(shù)就可以表示為:
1 2 3 4 5 6 7 8
0 0 1 1 0 1 0 1
看出規(guī)律來了嗎, 這其實(shí)就是卡特蘭數(shù)的變體, 假設(shè)我們現(xiàn)在有一個(gè) 不符合規(guī)則 的排隊(duì)順序:
1 2 5 3
4 6 7 8
那么對(duì)應(yīng)的0和1序列就是:
1 2 3 4 5 6 7 8
0 0 0 1 0 1 1 1
你會(huì)發(fā)現(xiàn), 不對(duì)啊, 這雖然5比3大違背了規(guī)則, 但是從01序列上來看, 其符合卡特蘭數(shù)的條件啊, 是不是錯(cuò)了, 其實(shí)沒錯(cuò), 因?yàn)槲覀兇颂幹豢紤]組合, 而不是排列, 因?yàn)槟銜?huì)發(fā)現(xiàn), 下面這個(gè)排隊(duì)順序與上面那個(gè)不符合規(guī)則的排隊(duì)順序, 生成的01序列是一樣的, 都是00010111:
1 2 3 5
4 6 7 8
也就是說, 對(duì)于一種01序列, 很顯然只有一種排隊(duì)方式是正確的, 是一一映射的關(guān)系, 譬如:
1 2 3 5 | 1 3 2 5 | 1 2 5 3 | 3 1 2 5 | 3 5 2 1 | 5 3 1 2 ...
4 6 7 8 | 4 7 6 8 | 4 6 8 7 | 7 4 6 8 | 7 8 6 4 | 8 7 4 6 ...
上述所有排隊(duì)方式, 都對(duì)應(yīng)著00010111, 但是其中只有一種是正確的, 也就是排隊(duì)方式跟01序列是一一對(duì)應(yīng)的映射關(guān)系, 那么也就是說, 我們可以完全不考慮排隊(duì)方式, 只考慮總共有多少種可行的01序列即可, 因?yàn)橐环N01序列對(duì)應(yīng)且只對(duì)應(yīng)著一種排隊(duì)方式
而我們實(shí)踐后發(fā)現(xiàn), 可行的01序列就是卡特蘭數(shù)的要求, 可以類比于出棧入棧問題, 即, 至此, 完全轉(zhuǎn)化為卡特蘭數(shù)問題
部分參考http://daybreakcx.is-programmer.com/posts/17315.html, https://www.cnblogs.com/wuyuegb2312/p/3016878.html#bop和https://www.cnblogs.com/13224ACMer/p/4671551.html
在寫這篇文章中, 瀏覽了很多博客, 如果有哪篇博客被引用我卻忘了指出, 懇請(qǐng)告知, 我即刻修改寇壳。