题目链接
题目概述
有一个棋盘从((0,0))走到((n,n))的方法有(C_{2n}^n)中,现在加一个限制,要求走的路不能穿过多角线(可以接触到对角线),计算满足这种限制条件的方法数.读取一个(n)输出方法数,读到(-1)结束程序执行.
解题思路
因为从对角线上方走和从对角线下方走是完全对称的,可以只考虑从对角线下方走,走到((n,n))的方法数,然后直接乘以(2)就可以了.从从((0,0))到((n,n))总的方法数是(C_{2n}^n),考虑剔除掉从对角线下方穿过对角线到达点((n,n))的,得到的就是不穿过对角线只经过对角线下方的方法数,然后将这个结果乘以(2)就是总的方法数.计算穿过对角线的方法采用的是向上平移对角线,计算从点((0,0))到((n-1,n+1))的方法数,这个部分是在穿过原来对角的路线在碰到新的对角线的部分做对称得到的,恰好是从((0,0))到((n-1,n+1)),然而那些对角线下方的不穿过对角线的方法数没法经过这样的变换到达((n-1,n+1)),从而排除掉从对角线下方穿过对角线到达点((n,n))的方法数.于是从对角线下方不穿过对角线到达点((n,n))的方法数是(C_{2n}^n-C_{n-1+n+1}^{n-1}).经过演算得到:
即从对角线右下方不穿过(但可以经过)对角线到达((n,n))的方法数是第一类(Catalan)数的第(n)项(C_n).
这个实际是<组合数学>这本书在讲(Catalan)数举的一个例子,是对前面定理的一个解释.假设将向上走记为(+1),将向右走记为(-1),现在求在对角线上方而不经过穿过对角线到达((n,n))的数目,也就转换成了这样一个问题,对于这样(n)个(-1)和(+1)组成的序列(代表路径),这个序列的每一个部分和都应该不小于(0),即:
这个用前面那个定理来解的话恰好是(C_n,Catalan)数的第(n)项的值.
然后是考虑(C_n)的计算了,定义那个计算的话不可行,((2n)!)很容易超出long long
的范围溢出,关于(Catalan)有两个非常重要的和前项有关的公式:
hdu2067这道题比较坑的一个地方是,即使开了long long
,如果用那个(C_n,C_{n-1})的递推关系算的话,不做处理的话会在34项溢出,而题目中的(n)的范围是(1leq n leq 35),所以交上去会WA;而如果用第一个那个式子计算的话,会在36项开始溢出,但是刚刚好可以正确计算(n=35).
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = 40;
typedef long long ll;
ll C[N];
// // 利用递推式计算,long long 34项就溢出了
// void calculate(){
// C[0] = 1;
// for (int i = 1; i < N; i++){
// C[i] = (4 * i - 2) * C[i - 1] / (i + 1);
// }
// }
// 利用前面项的乘积和累加和,36项开始溢出
void calculate(){
C[0] = 1;
for (int i = 1; i < N; i++){
for (int j = 0; j < i; j++){
C[i] += C[j] * C[i- 1 - j];
}
}
}
int main(int argc, const char** argv) {
calculate();
// for (int i = 0; i < N; i++)
// printf("%-3d:%lld
",i,C[i]);
int cnt = 1;
int n;
while (scanf("%d", &n) && n != -1){
printf("%d %d %lld
", cnt, n, C[n]*2);
++cnt;
}
return 0;
}
其它
定理:考虑由(n)个(+1)和(n)个(-1)组成的(2n)项序列:
[a_1,a_2,a_3,dots,a_{2n} ]其部分和满足:
[a_1+a2+dots+a_k ge 0, qquad (k = 1 , 2, 3,dots, 2n) ]的序列的个数是第(n)个(Catalan)数:
[C_n=frac{1}{n+1}C_{2n}^n ]把一个(n+1)条边的凸多边形区域利用插入再区域内部的不相交的对角线而划分成三角形的方法数等于在给定顺序的(n)个数的乘法方案数,数值是(C_{n-1}).