11085 买票找零
时间限制:800MS 内存限制:65535K
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC
Description
一场激烈足球赛即将开始,售票员紧张地卖票着……。 每张球票50元,现在有2n(1<=n<=18)个球迷排队购票,其中n个手持50元钞票,另外n个手持100元钞票。 假设开始售票时售票处没有零钱可以找零。 问这2n个人有多少种排队方式,不至使售票处出现找不出零的局面? 例如当n=3时,共6人,3人持50元,3人持100元。可以找零的排队方式有如下5种: 50 50 50 100 100 100 50 50 100 100 50 100 50 50 100 50 100 100 50 100 50 50 100 100 50 100 50 100 50 100
输入格式
输入:输入n,表示2n个球迷,其中n个手持50元,另外n个手持100元。(n<=18)
输出格式
输出:这2n个人可以找零的排队方式数。
输入样例
3
输出样例
5
提示
这个问题可以联想到括号匹配问题。把手持50元的球迷看成左括号“(”,而手持100元的球迷看成右括号“)”。 要求任意一个右括号都要有一个前缀序列中的左括号与之对应,所以始终找的开钱的排队方式对应着合法的括号排列。 什么样的序列是合法序列? 可以这样理解:遍历n个左括号和n个右括号排成的队列,当前符号如果是左括号,入栈; 若是右括号,让栈尾左括号出栈,如果能始终保证栈中有足够的左括号,那么该排列是一个合法的排列。 若排成的队列是奇数个,肯定不合法。但现在此题不是计算某一给定序列是否合法,而是分析合法序列的总数。 方法一 分析:最先第1个符号一定得是左括号,否则该排列肯定不合法。 假设第1个左括号和第k个符号匹配,那么从第2个至第(k-1)个符号,以及第(k+1)个符号到第2n个符号也都是合法 的括号序列。第2~第(k-1)个中间共k-2个符号,这k-2必是偶数;第(k+1)~第2n个中间共2n-k个符号,这2n-k也 必是偶数,即k必是偶数。若k为奇数,k-2为奇数,从第2~第(k-1)就含奇数个符号,那不合法。 |------|------|------...------|------|------|------...------|------| 1 2 ... k-1 k k+1 2n-1 2n |(----- 合法 ------)| |(----- 合法 ------)| 设2n个(偶数)符号中合法的括号序列个数为f(2n),若第1个符号(左括号)与第k个符号(右括号)匹配, 如前所分析,k是偶数,假设k=2i(i=1,...n)。 那么剩余符号的合法序列为:f(2i-2)*f(2n-2i), 1<=i<=n。 所以:f(2n) = sum{ f(2i-2)*f(2n-2i) | 1<=i<=n } = f(0)*f(2n-2) + f(2)*f(2n-4) +...+ f(2n-2)*f(0) 其中f(0)=1。 有了这个公式,我们可以在O(n^2)的时间求出f(2n),这也是一个Catalan数。 稍作变换,就可以看出这里的f(2n)和书上3.1节所提的Catalan数形式是一样的。 进一步得到通项公式:f(2n)=C(2n,n)/(n+1)。这个通项公式的推导书上却没说,得查阅相关数学资料了。 作为程序实现,你只需前一个展开公式即可实现了。 方法二 分析:如果符号序列的前k(k=1,2,...,2n-1)项中左括号的个数不少于右括号的个数(即大于等于), 则序列合法,否则非法。 (1)n个左括号和n个右括号的总的排列数为C(2n,n),即2n个位置,选择n个给左括号,剩下n个全给 右括号。 (2)我们考虑一下非法序列的个数。 在非法序列中,存在某(些)个k,使得序列前k项中左括号个数小于右括号个数。 取其中最小的k,必有序列前k项中左括号的个数比右括号的个数刚好少1个,而序列后2n-k个项中 左括号个数比右括号个数刚好多1个。 若将序列后2n-k个项中的左括号和右括号全部对调(左括号视为右括号,右括号视为左括号),对 调后则整个序列左括号个数为n-1个,右括号为n+1个。 一个非法序列对应于这样一个对调后序列,这是一一对应的。 而对调后的序列左括号个数为n-1个,右括号为n+1个。这样的序列有C(2n,n-1)个,也就是2n个位 置,选择n-1个给左括号,剩下n+1个全给右括号。 根据一一对应对调原则,非法序列个数也为C(2n,n-1)个。 (3)将步骤1和步骤2相减,则可行的排列方式数为:C(2n,n) - C(2n,n-1) = C(2n,n)/(n+1)。这也 同时验证了“方法一”中未证明而推导的通项公式。 提醒: 此题推荐大家使用方法一的展开公式: f(2n) = sum{ f(2i-2)*f(2n-2i) | 1<=i<=n } = f(0)*f(2n-2) + f(2)*f(2n-4) +...+ f(2n-2)*f(0) 其中f(0)=1。 你若想"取巧取简"用方法二的公式算的话会面临大整数的运算,因为n<=18呢。 我看有同学用double类型来算阶乘,然后按方法二的公式算,由于浮点数精度问题也许会让最后结果有误差的吧。
我的代码实现
1 #include<stdio.h> 2 3 #define N 30 4 int p[N], m[N][N]; 5 6 7 void BuytCTicketatalan(int n){ 8 for(int i=0;i<n+1;i++)p[i]==0; 9 p[1]=1; 10 for(int i=2;i<=n+1;i++){ 11 for(int k=1;k<=n;k++){ 12 m[k][i-k]=p[k]*p[i-k]; 13 p[i]+=m[k][i-k]; 14 } 15 } 16 } 17 18 19 int main(){ 20 int n; 21 scanf("%d",&n); 22 BuyTicketCatalan(n); 23 printf("%d",p[n+1]); 24 return 0; 25 }