北京大学实验报告格式

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

程序设计报告

00648098 汪瑜婧

我保证没有抄袭别人作业!

1. 题目内容

acm2084题,题名为game of connections。题目要求:首先输入一个正整数n,表示用

2n个数围成一个圆周。然后用n条线段把这2n个数两两相连,使它们成为n个数对,要求n

条线段互不相交。正整数n的范围是0到100。输出为一个长整数,表示满足条件的连法共

有多少种。

2. 算法分析

(1)分治法思想

考虑一个具有2n个数的圆周。且这2n个数的标号为1,2,??2n首先,选则其中编号为

1的数作为研究的对象。容易看出,这个数可以分别和其它2n-1个数成对,也就是共有2n-1

种连法。假设它与编号为a的数相连,那么两个数之间的线段就将圆周分成了两个区域。一

个区域内有a-2个数,另一个区域内有2n-a 由于连线两两不能相交,所以不同区域内的两个数不可能成对。于是,两个

区域互相不受影响,可以单独处理,符合分治法的要求。可以看出,这两个小区域分别

可以看做具有a-2个数,和2n-a个数的圆周。为使符合要求的连法存在,要求a-2和 2n-a

均为偶数,即a必须为偶数。

于是分治法的思想可以递归地描述如下:

(1) 考虑2n个数的圆周。对编号为1的数,枚举编号为a(a为偶数)的数与它成对。

(2) 递归计算a-2和2n-a个数的圆周分别有多少种连法,记为connect[(a-2)/2]和

connect[(2n-a)/2]。

(3) 计算公式为:connect[n]=∑connect[(a-2)/2]×connect[(2n-a)/2] a

算法框架:

int connect(int n)

{ int result=0; if (n<=1) return 1; for (int i=0;i<n;i++) //枚举可能的

成对

//分治法计算每种情况的连法数,再累加

result += result+connect(i)*connect(n-1-i); return result;

} (2)高精度计算

用上面的代码简单尝试过就可以发现,所求的连法个数随n的增加增长速度很快,在n=20

的时候就超过了长整数型long可以表示的范围。所以,本题的另外一个关键点是要采取高精

度计算。

为了确保程序的可读性,我们可以自定义大整数类longint,然后在上面算法框架的基

础上进行修改。将对int型变量的操作改为对longint数据类型的操作。并重载“+”,“*”,

“<<”等运算符来实现这些操作。

1)大整数类longint的adt

class longint {

public: };

没有定义大整数的长度这一私有成员变量。原因是这样对于本题来说已经足够了。如果

定义大整数的长度作为私有变量,在进行大整数的加法和乘法时能够减少一些运算,但维持

这一变量也会使程序变得相对比较繁琐。

2) 大整数加法

大整数加法的算法很简单。从最低位开始按位相加,并且每次都处理一次进位即可。也

可以在所有按位相加结束后,从低位到高位扫描,统一处理一次进位。这两种方法在运算次

数上是一样的。

说明:我个人的习惯是让数组的最后一位来表示个位,这样比较符合书写长整数的自然

顺序,容易理解。而很多算法书上使用数组的首位来表示各位,这两种表示顺序在代码实现

时会稍有不同,但在本质上是一样的。

重载“+”运算符:

longint longint::operator*(longint a) { } 3)大整数乘法

longint(); int num[60]; //初始化数组为全0 longint operator+(longint a);

longint operator*(longint a); friend ostream& operator<<(ostream&

output,longint& a); longint result; for (int i=59;i>20;i--) } return

result; for (int j=59;j>59-i;j--) result.num[i+j-59]+=num[i]*a.num[j];

//按位相加//统一处理进位for (i=59;i>0;i--){ result.num[i]%=10;

result.num[i-1]+=result.num[i]/10;

使用一个双重循环,让第一个大整数的每一位和第二个大整数的每一位分别相乘,并将

所得值加到表示结果的大整数正确的数位上。仍采取数组的最后一位表个位的表示顺序,则

第i位和第j位相乘的值应加在第i+j-59位上(数组大小为60)。

再考虑对进位的处理。如果每次运算都进行进位处理,需进行n2次处理,时间代价较大。

所以选择最后一并进行进位处理,从低位到高位扫描,只需进行n次运算。

重载“*”运算符:

longint longint::operator*(longint a) { }

4) 大整数的输出

由于大整数类的adt省略了对大整数长度的记录,所以先要跳过数组开头的

重载“<<”运算符:

{ }

3.

longint result; for (int i=59;i>20;i--) } return result; for (int

j=59;j>59-i;j--) result.num[i+j-59]+=num[i]*a.num[j]; //统一处理进位

//第i位和第j位相乘的值加在第i+j-59位上for

(i=59;i>0;i--){ result.num[i]%=10; result.num[i-1]+=result.num[i]/10; 数个0,

再进行输出。 ostream& operator<<(ostream& output,longint& a) int

i=0; while(a.num[i]==0) cout<<a.num[j]; i++; //跳过数组开头的数个0 for

(int j=i;j<=59;j++) return output; 算法的优化

(1)记录子问题的解

相关文档
最新文档