编程之美:第四章 数字之趣 4.3买票找零

/*
买票找零:
每张球票50元,现有2*n个人排队购票,其中有n个人手持50元的钞票,另外n个人手持100元的钞票,假设开始售票时没有零钱找。问这2*n个人有多少种排队方式,
不至于售票处出现找不开钱的局面?

注意至少要保证排在前面的持50元的人数>=持100元的人数

解法1:
从队首往后数,手持100元的球迷总比手持50元的球迷少,可以把钱召开。
联想到括号匹配问题。假设每个手持50元的球迷是一个左括号"(",而手持100元的球迷是一个右括号")",任意一个售出100元的球迷找回的50元都来自于之前一个
手持50元的球迷
用栈,遍历n个左括号和n个右括号,如果是左括号,则压栈中,如果是右括号,那么让栈顶的左括号出栈,如果始终能够保持栈中有足够的左括号,那么是一个合法
排列。

第0个符号一定是左括号,假设第0个左括号与第k个符号匹配,那么从第一个符号到第(k-1)个符号,第(k+1)个符号到第2n-1个符号也都是一个合法的括号序列。
而k肯定是奇数,否则第1个符号到第(k-1)个符号之间只有奇数个符号就不合法,设k = 2*i+1 (i = 0,1,...,n-1)

0 1 .... 2i 2i+1 2i+2 ... 2n-1
  ________       _____________
    2i个            2n-2i-2个
假设2n个符号中合法的括号序列个数为f(2n),若第0个括号(左括号)与第k=2i+1(i = 01,2,...,n-1)个括号(右括号)匹配,那么剩余括号的合法序列为:
f(2*i) * f(2*n-2*i-2)
得到递推式:
f(2n) = i从0到n-1 对f(2i)*f(2n-2i-2)累加排列数
      = [f(0)*f(2n-2)] + [f(2)*f(2n-4)] + [f(4)*f(2n-6)] + ... + [f(2n-2)*f(0)]
	  其中f(0) = 1,可以用O(n*m)的时间求出问题答案。f(0) = f(2*0) = 0个符号中合法的括号序列个数为1 ? 第0个符号一定是左括号,由于中间没有间隔元素,
	  第0个符号与第一个符号一定匹配,所以合法括号序列个数为1

解法2:
用1表示50元的球迷,0表示100元的球迷,那么2n个球迷的排队就对应n个1和n个0的排列,例如:1,1,0,0,0,...,1。如果序列的任意前k(k=1,2,...,2n-1)项中1的个数
都不少于0的个数,称这样的序列是合法的,否则称为非法的。合法的序列正好可与行的排列方式一一对应,同时,称由n-1个1和n+1个0组成的序列为Sigma序列
这样的序列总共有个[2*n]个。
                  [n-1]
n个1和n个0的总排列数位[2*n](2n个位置,选择n个位置给1,剩下的n个给0),即合法序列数和非法序列数的总和。
                      [n  ]
需要求和合法的序列数,先求解非法序列。
在一个非法序列中,存在某个k,使得序列前k项中1的个数小于0的个数。存在某个k,使得序列前k项中1的个数比0的个数刚好少一个,取得其中最小的k。那么这个
序列的后2n-k项中1的个数刚好比0的个数多1,然后2n-k项的0换为1,1换为0,得到一个新序列,有n-1个1和n+1个0组成的Sigma序列。
那么一个Sigma序列能唯一地对应到一个非法序列么?对任意一个Sigma序列,存在某个k,使得序列的前k项中1的个数少于0的个数(因为只有n-1个1,而n+1个0)。
存在某个k,使得前k项中1的个数比0的个数刚好少1个。取其中最小的k,将这个序列中2n-k项中的0变1,1变0,得到一个新序列:n个1和n个0.这个序列正是一个非法
序列。因此Sigma序列对应唯一一个非法序列。
非法序列个数为 {(n-1)                                           {n   _    {n-1    =    1/(n+1)*  {n
              C{2n     ,合法序列数等于总个数减去非法序列个数 = C{2n      C{2n                   C{2n

输入:
5(5人手持50,5人手持100)
输出:
42
*/

/*
关键:
1 return (int) (lMolecule/((n+1) * lDemonitator));//合法序列的总个数 = 1/(n+1) Cn 2n
2 n个1和n个0的总排列数位[2*n](2n个位置,选择n个位置给1,剩下的n个给0),即合法序列数和非法序列数的总和。
                        [n  ]
3 在一个非法序列中,存在某个k,使得序列前k项中1的个数小于0的个数。存在某个k,使得序列前k项中1的个数比0的个数刚好少一个,取得其中最小的k。那么这个
序列的后2n-k项中1的个数刚好比0的个数多1,然后2n-k项的0换为1,1换为0,得到一个新序列,有n-1个1和n+1个0组成的Sigma序列。
4 
非法序列个数为 {(n-1)                                           {n   _    {n-1    =    1/(n+1)*  {n
              C{2n     ,合法序列数等于总个数减去非法序列个数 = C{2n      C{2n                   C{2n
*/

#include <stdio.h>

int validPermutation(int n)
{
	long long lDemonitator = 1;
	for(int i = 1 ; i <= n; i++)
	{
		lDemonitator *= i;
	}
	long long lMolecule = 1;
	for(int i = 2*n ; i > n ; i--)
	{
		lMolecule *= i;
	}
	return (int) (lMolecule/((n+1) * lDemonitator));//合法序列的总个数 = 1/(n+1) Cn 2n
}

void process()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		printf("%d\n",validPermutation(n));
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值