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 }

 

 
posted on 2017-11-18 13:57  TinyRick  阅读(628)  评论(0编辑  收藏  举报