zoukankan      html  css  js  c++  java
  • 11085 买票找零

    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 }
    
    
    
     
  • 相关阅读:
    9
    8
    7
    6
    5
    第四周
    作业14-数据库
    作业13-网络
    作业12-流与文件
    作业11-多线程
  • 原文地址:https://www.cnblogs.com/double891/p/7856530.html
Copyright © 2011-2022 走看看