【初级擂台】排队序列

ZhangYv 2003-09-13 10:19:17
加精
问题描述:
盛况空前的足球赛即将举行。球赛门票售票处排起了球迷购票长龙。按售票处规定,每位购票者限购一张门票,且每张票售价为50元。在排成长龙的球迷中有n个人手持面值50元的钱币,另有n个人手持面值100元的钱币。假设售票处在开始售票时没有零钱。试问这2*n个球迷有多少种排队方式可使售票处不致出现找不出钱的尴尬局面。
例如当n=2时,用A表示手持面值50元钱币的球迷,用B表示手持面值100元钱币的球迷,则最多可得到以下2组不同排队方式,使售票处不致出现找不出钱的尴尬局面。

售票处 A A B B
售票处 A B A B


编程任务:对于给定的n的值( 0 <= n <= 20),编程计算出2*n个球迷有多少种排队方式可使售票处不致出现找不出钱的尴尬局面。

数据输入:由键盘输入n的值

结果输出: 程序运行结束时,将计算出的排队方式数输出到屏幕。

输入示例
3

输出示例
5
...全文
100 35 打赏 收藏 转发到动态 举报
写回复
用AI写文章
35 条回复
切换为时间正序
请发表友善的回复…
发表回复
ZhangYv 2003-09-24
  • 打赏
  • 举报
回复
to xmmmj:
哦哦,我忘记说明了。推荐好书 组合数学(原书第3版),机械工业出版社的,在特殊计数有对CATALAN数的公式给证明,或者其他组合数学的书都对这个计数有介绍。
Besich 2003-09-24
  • 打赏
  • 举报
回复
没有机子,初步思考...
比方说人排成的队 是s的话
那么对s[k]之前的有50的人的人数必须多于100的人数,保证这样就可以!保证这样的话,应该能弄个数学式子出来......
我回去考虑考虑在来
Besich 2003-09-24
  • 打赏
  • 举报
回复
找到了!
设100的为b[m],50的为a[n]
首先必须n>m,否则无解!
然后
b[k]最前只能放到2*k处,也就是b[k]必须放到第2*k以后!
这样子就可以推出公式了!!!!!
最后一个有m+n-2*m种放法, 倒数第二个有m+n-2*m+2种放法......
这个总共的放法可以求出来!就用求和公式,公差是2!!!
然后用这式子去除 m! (注意,是m的阶层)
xmmmj 2003-09-23
  • 打赏
  • 举报
回复
请问:如何证明一个不合要求的序列和有(m+1)个+1和(n-1)个-1的序列是一一对应的??
ZhangYv 2003-09-23
  • 打赏
  • 举报
回复
frankzch(西方失败) 和 theoldman都给出的程序都是按你所说的思路,使用递归堆栈模拟的效率太低了。那个数列是有通项公式的,你可以再认真考虑一下我上面给出的解答。
xmmmj 2003-09-22
  • 打赏
  • 举报
回复
往前插队的方法数,可以用数列的展开来实现
我最后给的结果是个展开数列的过程,应该用一个链表就可以实现
这个数列的展开好像没有通项公式。
ZhangYv 2003-09-22
  • 打赏
  • 举报
回复
xmmmj(可笑):
你的就是模拟排队的过程,上面我给出了利用数学方法直接推导的过程。

kbsoft:
没有,那次上课只是简单提一下这题推导公式的做法,上面是我自己写的...
kbsoft 2003-09-22
  • 打赏
  • 举报
回复
TO ZhangYv:
这是你们的讲稿么?能把相关资料发我么?
kbsoft 2003-09-22
  • 打赏
  • 举报
回复
很不错(起码我这么认为)
xmmmj 2003-09-21
  • 打赏
  • 举报
回复
用插队算法, 先abababababababab。。。。。排好。a,50元 b 100元。然后带50元的可以往前插,并保持五十元人的相对顺序不变。
结果等于把一个数列不断展开
1
1 2
1 2 1 2 3
12 123 12 123 1234
1变12 2变123 3变1234
展开n-1次最后的数列个数就是总的可能数
ZhangYv 2003-09-21
  • 打赏
  • 举报
回复
下面分析一下求组合数的方法,程序在DEV-C++下通过:

#include <iostream>
#include <stdlib.h>
#include <windows.h>
long long fac1(long n, long m)
{
long i;
long long result = 1;
if (n < m)
return 0;
//result = C(n,m) = n! / (m! * (n-m)!)
for (i = m + 1; i <= n; ++i)
result *= i; //从m+1到n的数连乘,这段代码会让result迅速溢出
for (i = 1; i <= n - m; ++i)
result /= i;
return result;
}

long long fac2(long n, long &m)
{
long i, j;
long long result = 1;
if (n < m)
return 0;
if (n / 2 < m)
m = n - m; // C(n,m) = C(n,n-m)

//result = C(n,m) = C(n-1, m-1) + C(n-1, m)
//由C(n,m) = n! / (m! * (n-m)!)容易推导出 C(i,j) = C(i,j-1) * (i-j) / (j+1)
for (i = n - m + 1, j = 1, result = n - m + 1; i < n; ++i, ++j)
result += result * (i - j) / ( j + 1);
return result;
}

int main()
{
long long result;
long n, m;
LARGE_INTEGER t1,t2; //对比算法的执行效率
cout << "Computing C(n,m), Input N,M = ";
cin >> n >> m;

QueryPerformanceCounter(&t1); //取起始时间
result = fac1(n,m);
QueryPerformanceCounter(&t2); //取终止时间
cout << "C(n, m) = " << result << endl;
cout << "fac1's Running Time: " << t2.QuadPart-t1.QuadPart << endl; //算法运行时间

QueryPerformanceCounter(&t1); //取起始时间
result = fac2(n,m);
QueryPerformanceCounter(&t2); //取终止时间
cout << "C(n, m) = " << result << endl;
cout << "fac2's Running Time: " << t2.QuadPart-t1.QuadPart << endl; //算法运行时间
system("pause");
return 0;
}

比较上述两种求组合数的方法,测试数据为C(26,13){N = 26, M = 13}时,fac1算的结果即将溢出,且运行速度fac2大约比fac1快4倍;对于测试数据为C(100,50)是,fac1早就溢出了(大约在C(30,X)时候),fac2还是正常的。实际上fac2的优化,在时间效率上比fac1略为提高;但在空间上的效率要远远高于fac1的代码,fac1在算连乘时候做了许多不必要的大数乘法消耗了时间和空间。
这个问题的最后结果:
由于问题的解太大,我们必须自己做高精度的运算。自己做LargeInt类型,它是有8000位的高精度整数,重载+,-,*,/,=运算符号(搜旧贴,代码略),利用上述求组合数的方法解得C(n,m)的值。


总结:
综合上述分析,我们容易发现提高程序效率的重要性和一些技巧!
1. 用数学方法求解这个问题比用模拟的方法快了几个数量级!
2. 优化算法可以利用直觉寻找已知模型或者利用数学进行理性的推导。
3. 我们在优化算法后发现实现编码过程会遇到几个问题,我们还要优化代码的时间和空间效率。
4. 优化代码通常可以利用“编程经验或者技巧”或者利用“数学手段”来实现,对于上面的程序我们利用了数学公式来简化求解组合数的程序段。
5. 优化程序的效率往往比编出正确的程序更难。
6. 一个优秀的程序不论代码和运行都是十分协调,优美的。我们需要完美主义 :)

END.

这个擂台自问自答,FT~~~~~~~~~~
ShallowShrimp 2003-09-20
  • 打赏
  • 举报
回复
算法版真没趣,炒来炒去基本上都是以前的老题目,没什么新意!
积分又特别困难,逛了几天,还没拿到一分,郁闷中~~~
ZhangYv 2003-09-20
  • 打赏
  • 举报
回复
求解M != N的情况,我们可以参考求Catalan数的方法,以下我们从组合数学上来推导:

很显然50元的进入售票处我们可以看做+1,如果100元的进入则可以看做-1。如果有m个+1和n个-1时,这个问题的目标就是求:
序列A[1]A[2]A[3]...A[m+n],满足约束条件是A[1]+A[2]+A[3]+ ... A[k] >= 0 (k <= m+n)
考虑所有m+n个数的序列有(m+n)!/(m!*n!) = C(m+n,n)个,再利用加法原理扣出其中不要求的。对于一个不合法的序列,存在着一个最小的k满足,A[1]+A[2]+...A[k] < 0.因为k是不合法序列中最小的项,所以可以断定A[1]+A[2]+...A[k-1] = 0,即前k-1项中有相同的+1和-1,且A[k] = -1(且k必为奇数)。如我们把前k项都取相反数(-A[k] = 1,前k-1项还是有相同的个数+1和-1),新生成的序列则共有(m+1)个+1和(n-1)个-1。并且可以证明一个不合要求的序列和有(m+1)个+1和(n-1)个-1的序列是一一对应的,所以不合法的序列有C(m+n,n-1) = (m+n)!/((m+1)!(n-1)!)种。故本题的解是:

C(m+n,n)-C(m+n,n-1) =

m-n+1
------- * C(m+n,n)
m+1

排队序列总数就计算C(m+n,n) * (m-n+1)/(m+1)的值。

还没有结束...可以发现这个问题的规模很大:
在存储空间上:我们需要自己做高精度的整数类型和相应使用到的运算来求那个式子的值;
在计算时间上:我们需要更快的方法来求组合数C(a,b)的值,因为直接利用C(a,b) = a!/(b! * (a-b)!)来求的效率很显然是太低了,所以还需要一些技巧还实现求解组合数。

到此问题的算法已经得出来了,往下该考虑如何编程和优化代码。
ZhangYv 2003-09-20
  • 打赏
  • 举报
回复
这种现象主要是水平两级分化造成的,以前排行版前5名都是1-3千分,后面就有得几十分/月的也上排行版 :(
当然这种不是好现象,以后要靠你我尽力改观了。
ZhangYv 2003-09-20
  • 打赏
  • 举报
回复
整个专题开发拿分都很不容易的,结贴给出的80%的专家分会集中在20%的人手里。经典的问题一般都是老题目了,什么叫有新意?难道要到国外去搜先最先进最前沿的题目过来做,这似乎不太现实。
这里拿分不比开发语言或者其他版那么容易,楼上别带着浮躁的心理学习。
你看看8月份的专题开发版的专家分排行就知道拿分很困难的:

1 happy__888 4866 做3D好累呀
2 BlueSky2008 2151
3 zzwu 912 ReadMuch,ThinkMore,TalkLess
4 ZhangYv 793
5 Riemann 740 人生就是奋斗的一生
6 loewe 561 请多多关照
7 EndDuke 515 我是大学生
8 mmmcd 455 君子于役,不日不夜
9 ljranby 410 人工智能算法的狂热者
10 HUNTON 381
11 noslopforever 309 曾经的选择,已经坚定了未来的脚步
12 maplexp 301
13 kbsoft 282 32岁之前能赶上BILL吗?
14 DarthVader 220
15 feixue3000 200
16 canel 200
17 kjackyboy 200 成事在天,谋事在人
18 coolvoldo 199 Welcome to coolvoldo.yeah.net
19 ttmmdd 180 去他的!
20 boodweb 174 Nothing to say

你每个月拿100分左右就可以进算法版小类的排行版了 :)
祝以后你的分数多多 ^^
curlynan 2003-09-19
  • 打赏
  • 举报
回复
小弟曾经在一本期刊上看过一篇瀚挪塔问题的非递归算法的分析。很好,对于大规模问题,用递归算法,不仅太占内存,而且容易死机。但用非递归算法,效率非常非常高。
theoldman 2003-09-17
  • 打赏
  • 举报
回复
仍是递归,虽是笨拙但供参考。(与frankzch(西方失败)相比,时间复杂度稍优)

#include "iostream.h"

long Function(int m, int n)
{
if (n>m || n<=0 || m<=0) return -1 ;
if(n==1) return m ;
long result =0 ;

for(int i=0; i<m-n+1; i++)
result += Function(m-i, n-1) ;
return result ;
}

void main()
{
int m,n ;
cin >> m >> n ;
cout << "共有排队数为: " << Function(m,n) << "种" << endl ;
}

duanma 2003-09-17
  • 打赏
  • 举报
回复
mark
ZhangYv 2003-09-17
  • 打赏
  • 举报
回复
这个问题可以从很多个角度来考虑,从最初的M == N的情况开始:

1. 先利用直觉来寻找目标问题是否和我们已知的一个现成模型是一样或类似的,这是我们最容易采用的方法。进一步考虑这个问题的特性。持50元货币的在何种情况下都可以进入排队序列,持100元的若要进入排队序列,如果中有50元的未找开就可以进入序列并且找出这50元的零钱,否则就不能进入。这个特性是否很像“栈”?50元的随时可以入栈,当遇到100元要进入排队序列中相当于"栈不为空"时,让栈顶元素出栈。可见这个问题可以归结为对“有N个元素的出栈序列”的计数问题。 如果你明白问题的本质是如此,利用推导或者直接搜旧贴就知道:

|- 1 n = 0;
Sn = -| n-1
|- Σ S(i)S(n-i-1) n >= 1;
i=0
解递归方程即得C(2n,n)/(n+1)

2. 从数学角度思考是比较稳当的方法,很显然50元的进入售票处我们可以看做+1,如果100元的进入则可以看做-1。如果有N个+1和N个-1时,这个问题的目标就是求:
序列A[1]A[2]A[3]...A[2*n],满足约束条件是A[1]+A[2]+A[3]+ ... A[k] >= 0 (k <= 2*n)
这就是Catalan(卡特兰数)的原定义,所以很直接的可以得出解是C(2n,n)/(n+1)

到此这个问题解决了一部分,大家可以继续讨论M != N时的情况。
ZhangYv 2003-09-16
  • 打赏
  • 举报
回复
这道题利用组合数学可以推出解来。对于M = N的情况解就是1楼的答案,这个问题扩展到M != N时的更一般情况,但是问题的本质时和M=N没有什么不同的。给个提示:这是关于组合数学中的经典的“特殊数列”计数问题,我出这个问题主要想说明一些数学方法在解决问题时的重要性,可以大大提高算法时间和空间的效率。
加载更多回复(15)

33,008

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧