[Catalan数三连]网格有趣的数列树屋阶梯
?
如何讓孩子愛上打表
Catalan數
Catalan數是組合數學中一個常出現在各種計數問題中的數列。
以比利時的數學家歐仁·查理·卡塔蘭 (1814–1894)的名字來命名。
先丟個公式(設第n項為$h_n$):
$h_n=h_0*h_{n-1}+h_1*h_{n-2}+...+h_{n-1}*h_0,(n \ge 2)$
$h_n=\frac{h_{n-1}*(4n-2)}{n+1}$
$h_n=C_{2n}^n-C_{2n}^{n-1}=\frac{C_{2n}^n}{n+1}$
?
應用
出棧次序是卡特蘭數的一個應用。
我們將入棧視為+1,出棧視為-1,則限制條件為在任意位置前綴和不小于0 。
我們討論這個問題與卡特蘭數有什么關系。 為了方便,我們按入棧的先后順序將各個元素由1到n編號。
假設最后一個出棧的數為k。 則在k入棧之前,比k小的數一定全部出棧,所以這部分方案數為h(k-1)。
在k入棧之后,比k大的數在k入棧之后入棧,在k出棧之前出棧,所以這部分的方案數為h(n-k)。
這兩部分互不干擾,則方案數為h(k-1)*h(n-k) 枚舉k,得到的公式就是卡特蘭數的遞推公式。
——WangKoala
?
大部分Catalan數的題目都可以抽象為這樣一個模型,所以深刻理解第一個遞推式對快速分析出題目與Catalan有關很有幫助。
(其實還是打表最快hhh)
另外,有的題目則根據Catalan數的組合意義構造模型,比如下面的第一道題。
題目
3907: 網格
Time Limit: 1 Sec??Memory Limit: 256 MBSubmit: 1035??Solved: 367
[Submit][Status][Discuss]
Description
某城市的街道呈網格狀,左下角坐標為A(0, 0),右上角坐標為B(n, m),其中n >= m。現在從A(0, 0)點出發,只能沿著街道向正右方或者正上方行走,且不能經過圖示中直線左上方的點,即任何途徑的點(x, y)都要滿足x >= y,請問在這些前提下,到達B(n, m)有多少種走法。Input
輸入文件中僅有一行,包含兩個整數n和m,表示城市街區的規模。
Output
輸出文件中僅有一個整數和一個換行/回車符,表示不同的方案總數。
Sample Input
6 6Sample Output
132HINT
100%的數據中,1 <= m <= n <= 5 000首先考慮n*n的情況。不難發現此時答案即為Catalan數。
如果沒有線的限制,從$(0,0)-->(n,n)$的總方案數為$C_{2n}^n$
考慮它的含義:$2n$次操作,其中選$n$次向上走
接下來需要考慮不合法的情況并減去它。
黃線可以看作合法與不合法情況的分界(一碰就不合法)
我們將矩形沿這條線對稱過去,那么碰到黃線后走到$(n,n)$的走法 就可以對稱為碰到黃線走到$(n-1,n+1)$的走法。
那么顯然不合法方案數為$C_{2n}^{n-1}$。($2n$次操作中有$n-1$次向右,你把它寫成$C_{2n}^{n+1}$也無所謂 反正它們相等)
?
至于$n!=m$的情況,以此類推即可。
$ans=C_{n+m}^n-C_{n+m}^{m-1}$?
對于組合數計算,分解質因數約分后用高精乘低精統計即可。沒必要高精除。
#include<cstdio> #include<iostream> #include<cstring> #include<vector> using namespace std; int n,m; int pri[1600],tot,vis[10005]; int bu[1600],num1[100005],num2[100005],ans[100005]; void getpri() {for(int i=2;i<=10000;i++){if(!vis[i])pri[++tot]=i;for(int j=1;j<=tot;j++){if(i*pri[j]>10000)break;vis[i*pri[j]]=1;if(i%pri[j]==0)break;}} } void print(int a[]) {for(int i=a[0];i>=1;i--)printf("%d",a[i]);puts(" "); } void div1(int x) {for(int i=1;pri[i]<=x&&x!=1;i++)while(x%pri[i]==0)x/=pri[i],bu[i]++; } void div2(int x) {for(int i=1;pri[i]<=x&&x!=1;i++)while(x%pri[i]==0)x/=pri[i],bu[i]--; } void mult(int x,int a[]) {int k=0;for(int i=1;i<=a[0];i++){int tmp=a[i]*x+k;a[i]=tmp%10;k=tmp/10;}while(k)a[++a[0]]=k%10,k/=10; } void Minus(int a[],int b[]) {int j=1,x=0;while(j<=a[0]||j<=b[0]){if(a[j]<b[j]){a[j]+=10;a[j+1]--;}ans[j]=a[j]-b[j];j++;}int k=j;while(ans[k]==0&&k>1)k--;ans[0]=k; } int main() {getpri();scanf("%d%d",&n,&m); /* div1(n);for(int i=1;i<=10;i++)cout<<bu[i]<<' ';div2(m);for(int i=1;i<=10;i++)cout<<bu[i]<<' ';*/for(int i=n+m;i>=m+1;i--)div1(i);for(int i=n+1;i>=2;i--)div2(i);num1[0]=num1[1]=1;for(int i=1;i<=1600;i++){if(!pri[i])break;if(!bu[i])continue;while(bu[i])mult(pri[i],num1),bu[i]--;}//print(num1);for(int i=0;i<=num1[0];i++)num2[i]=num1[i];mult(n+1,num1);mult(m,num2);//print(num1);print(num2); Minus(num1,num2);print(ans);return 0; } View Code?
1485: [HNOI2009]有趣的數列
Time Limit: 10 Sec??Memory Limit: 64 MBSubmit: 2252??Solved: 1205
[Submit][Status][Discuss]
Description
?我們稱一個長度為2n的數列是有趣的,當且僅當該數列滿足以下三個條件:
??? (1)它是從1到2n共2n個整數的一個排列{ai};
??? (2)所有的奇數項滿足a1<a3<…<a2n-1,所有的偶數項滿足a2<a4<…<a2n;
??? (3)任意相鄰的兩項a2i-1與a2i(1≤i≤n)滿足奇數項小于偶數項,即:a2i-1<a2i。
??? 現在的任務是:對于給定的n,請求出有多少個不同的長度為2n的有趣的數列。因為最后的答案可能很大,所以只要求輸出答案 mod P的值。
Input
輸入文件只包含用空格隔開的兩個整數n和P。輸入數據保證,50%的數據滿足n≤1000,100%的數據滿足n≤1000000且P≤1000000000。
Output
僅含一個整數,表示不同的長度為2n的有趣的數列個數mod P的值。
Sample Input
3 10Sample Output
5
對應的5個有趣的數列分別為(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。
?
?
打表找規律可得答案為Catalan數。
這題如果強行想的話會很困難 而且比較浪費時間 所以不如直接打表找規律。
?還是分解質因數約分,統計時取模即可。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; ll mod,ans=1; int n,pri[150005],tot,vis[2000005]; int bu[150005],maxi=0,res[2000005]; void getprime() {for(int i=2;i<=2*n;i++){if(!vis[i])pri[++tot]=i,res[i]=tot;for(int j=1;j<=tot;j++){if(i*pri[j]>2*n)break;vis[i*pri[j]]=1;res[i*pri[j]]=j;if(i%pri[j]==0)break;}} } void divi(int x,int val) {while(x!=1)bu[res[x]]+=val,x/=pri[res[x]]; } int main() {scanf("%d%lld",&n,&mod);getprime();for(int i=2*n;i>=n+1;i--)divi(i,1);for(int i=n+1;i>=2;i--)divi(i,-1);for(int i=1;i<=tot;i++)while(bu[i]--)ans=1LL*pri[i]*ans%mod;cout<<ans<<endl;return 0; } View Code?
2822: [AHOI2012]樹屋階梯
Time Limit: 1 Sec??Memory Limit: 128 MBSubmit: 1204??Solved: 716
[Submit][Status][Discuss]
Description
暑假期間,小龍報名了一個模擬野外生存作戰訓練班來鍛煉體魄,訓練的第一個晚上,教官就給他們出了個難題。由于地上露營濕氣重,必須選擇在高處的樹屋露營。小龍分配的樹屋建立在一顆高度為N+1尺(N為正整數)的大樹上,正當他發愁怎么爬上去的時候,發現旁邊堆滿了一些空心四方鋼材(如圖1.1),經過觀察和測量,這些鋼材截面的寬和高大小不一,但都是1尺的整數倍,教官命令隊員們每人選取N個空心鋼材來搭建一個總高度為N尺的階梯來進入樹屋,該階梯每一步臺階的高度為1尺,寬度也為1尺。如果這些鋼材有各種尺寸,且每種尺寸數量充足,那么小龍可以有多少種搭建方法?(注:為了避免夜里踏空,鋼材空心的一面絕對不可以向上。)?
?? 以樹屋高度為4尺、階梯高度N=3尺為例,小龍一共有如圖1.2所示的5種
?? 搭 建方法:
??
?
Input
一個正整數 N(1≤N≤500),表示階梯的高度
Output
一個正整數,表示搭建方法的個數。(注:搭建方法個數可能很大。)
Sample Input
3Sample Output
5HINT
1? ≤N≤500
?
?
一個大小為i的階梯,都可以看作由左上角一塊j和右下角一塊i-j-1的階梯,再用矩形填充空缺構成。
這樣構成的階梯算下來正好是用i個鋼材,且方案各不相同。
j在0--i-1取值,可得方案數的遞推式:
$h_n=h_0*h_{n-1}+h_1*h_{n-2}+...+h_{n-1}*h_0,(n \ge 2)$
這顯然就是Catalan的遞推式。
?
#include<cstdio> #include<iostream> #include<cstring> using namespace std; int n; int pri[1000005],tot,vis[1000005],res[1000005]; int bu[1000005],ans[50005]; void getpri() {for(int i=2;i<=2*n;i++){if(!vis[i])pri[++tot]=i,res[i]=tot;;for(int j=1;j<=tot;j++){if(i*pri[i]>2*n)break;vis[i*pri[j]]=1;res[i*pri[j]]=j;if(i%pri[j]==0)break;}} } void print(int a[]) {for(int i=a[0];i>=1;i--)printf("%d",a[i]);puts(" "); } void divi(int x,int val) {while(x!=1)bu[res[x]]+=val,x/=pri[res[x]]; } void mult(int x,int a[]) {int k=0;for(int i=1;i<=a[0];i++){int tmp=a[i]*x+k;a[i]=tmp%10;k=tmp/10;}while(k)a[++a[0]]=k%10,k/=10; } int main() {scanf("%d",&n);getpri();for(int i=2*n;i>=n+1;i--)divi(i,1);for(int i=n+1;i>=1;i--)divi(i,-1);ans[0]=ans[1]=1;for(int i=1;i<=tot;i++)while(bu[i]--)mult(pri[i],ans);print(ans);return 0; } View Code?
轉載于:https://www.cnblogs.com/Rorschach-XR/p/11222624.html
總結
以上是生活随笔為你收集整理的[Catalan数三连]网格有趣的数列树屋阶梯的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Jenkins中获取GitHub对应R
- 下一篇: java中debug使用