NOIP 2010普及组初赛试题及解析
- 一. 单项选择题 (共20题,每题1.5分,共计30分。每题有且仅有一个正确答案.)。
- 二. 问题求解(共2题,每题5分,共计10分)
- 三. 阅读程序写结果(共4题,每题8分,共计32分)
- 四. 完善程序 (前4空,每空2.5分,后6空,每空3分,共28分)
一. 单项选择题 (共20题,每题1.5分,共计30分。每题有且仅有一个正确答案.)。
1、2E+03 表示( )
A. 2.03
B. 5
C. 8
D. 2000
正确答案:D
2E+03 是一个科学记数法的表示方式。在这种表示法中,E 或 e 用于表示 10 的幂。因此,2E+03 可以被解释为 2 乘以 10 的 3 次方,即 2 ∗ 1 0 3 = 2000 2 * 10^3 = 2000 2∗103=2000。
2、一个字节(byte)由( )个二进制位组成。
A. 8
B. 16
C. 32
D. 以上都有可能
正确答案:A
一个字节(byte)由 8 个二进制位(bit)组成。这是计算机科学中的基础概念,广泛应用于数据存储和传输。
3、以下逻辑表达式的值恒为真的是( )。
A. P∨(¬P∧Q)∨(¬P∧¬Q)
B. Q∨(¬P∧Q)∨(P∧¬Q)
C. P∨Q∨(P∧¬Q)∨(¬P∧Q)
D. P∨¬Q∨(P∧¬Q)∨(¬P∧¬Q)
正确答案:A
p | q | p∧q(且) | p∨q(或) | ┐p(非) | ┐q |
---|---|---|---|---|---|
真 | 真 | 真 | 真 | 假 | 假 |
真 | 假 | 假 | 真 | 假 | 真 |
假 | 真 | 假 | 真 | 真 | 假 |
假 | 假 | 假 | 假 | 真 | 真 |
总结:一真全真、一假全假
A项:P ∨ (¬P ∧ Q) ∨ (¬P ∧ ¬Q)
P真Q真:
P ∨ (¬P ∧ Q) ∨ (¬P ∧ ¬Q)
由于P为真,(P ∨ …)这部分已经为真,因此整个表达式为真。
P真Q假:
P ∨ (¬P ∧ Q) ∨ (¬P ∧ ¬Q)
由于P为真,(P ∨ …)这部分已经为真,因此整个表达式为真。
P假Q真:
P ∨ (¬P ∧ Q) ∨ (¬P ∧ ¬Q)
由于P为假,¬P为真,Q为真,因此(¬P ∧ Q)这部分为真,整个表达式为真。
P假Q假:
P ∨ (¬P ∧ Q) ∨ (¬P ∧ ¬Q)
由于P和Q都为假,¬P和¬Q都为真,因此(¬P ∧ ¬Q)这部分为真,整个表达式为真。
B项:Q ∨ (¬P ∧ Q) ∨ (P ∧ ¬Q)
P真Q真:
Q ∨ (¬P ∧ Q) ∨ (P ∧ ¬Q)
由于Q为真,整个表达式为真。
P真Q假:
Q ∨ (¬P ∧ Q) ∨ (P ∧ ¬Q)
Q为假,但(P ∧ ¬Q)为真(因为Q为假),所以整个表达式为真。
P假Q真:
Q ∨ (¬P ∧ Q) ∨ (P ∧ ¬Q)
Q为真,所以整个表达式为真。
P假Q假:
Q ∨ (¬P ∧ Q) ∨ (P ∧ ¬Q)
由于P和Q都为假,(P ∧ ¬Q)和(¬P ∧ Q)都为假,Q也为假,所以整个表达式为假
C项:P ∨ Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ Q)
P真Q真:
P ∨ Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ Q)
P为真,所以整个表达式为真。
P真Q假:
P ∨ Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ Q)
P为真,所以整个表达式为真。
P假Q真:
P ∨ Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ Q)
Q为真,所以整个表达式为真。
P假Q假:
P ∨ Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ Q)
由于P和Q都为假,(P ∧ ¬Q)和(¬P ∧ Q)都为假,P和Q也都为假,所以整个表达式为假。
D项:P ∨ ¬Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ ¬Q)
P真Q真:
P ∨ ¬Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ ¬Q)
因为P为真,所以P ∨ ¬Q这部分已经为真,因此整个表达式为真。
P真Q假:
P ∨ ¬Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ ¬Q)
在这种情况下,P为真,Q为假,所以¬Q为真。由于P ∨ ¬Q这部分已经为真,整个表达式为真。
P假Q真:
P ∨ ¬Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ ¬Q)
因为P为假,Q为真,所以¬Q为假。此时,P ∧ ¬Q和¬P ∧ ¬Q也都为假。因此,整个表达式为假。
P假Q假:
P ∨ ¬Q ∨ (P ∧ ¬Q) ∨ (¬P ∧ ¬Q)
在这种情况下,P和Q都为假,所以¬P和¬Q都为真。因此,(¬P ∧ ¬Q)这部分为真,整个表达式为真。
4、Linux 下可执行文件的默认扩展名为( )
A. exe
B. com
C. dll
D. 以上都不是
正确答案:D
Linux 下的可执行文件通常没有特定的扩展名。这与 Windows 系统不同,Windows 系统通常使用 .exe 作为可执行文件的扩展名。在 Linux 中,文件是否可执行是由其文件内容中的“可执行”位决定的,而不是由其扩展名决定的。你可以使用 chmod 命令来更改文件的权限,使其变为可执行。
例如,一个名为 my_program 的文件,即使它没有 .exe 或其他扩展名,你也可以通过以下命令使其变得可执行:
chmod +x my_program
然后,你可以直接运行它:
./my_program
注意,文件前面有一个 ./,这是因为 Linux 通常只从当前目录或 PATH 环境变量中指定的目录运行可执行文件。使用 ./ 告诉系统在当前目录中查找该文件并执行它。
5、如果树根算第 1 层,那么一棵 n 层的二叉树最多有( )个结点
A. 2 n − 1 2^n - 1 2n−1
B. 2 n 2^n 2n
C. 2 n + 1 2^n + 1 2n+1
D. 2 n + 1 2^{n + 1} 2n+1
正确答案:A
考察二叉树的性质。
性质1:在二叉树的第i层,最多有2i-1个结点(i>=1)
性质2:深度为k的二叉树,至多有2K-1个结点(K>=1)
对于一棵 n 层的二叉树,我们可以按照以下方式计算其最多有多少个节点:
在第 1 层(即根节点层),有 1 个节点。
在第 2 层,每个节点可以有两个子节点,因此有 2 个节点。
在第 3 层,每个第 2 层的节点可以有两个子节点,因此有 2^2 = 4 个节点。
…
在第 n 层,每个第 n-1 层的节点可以有两个子节点,因此有 2^(n-1) 个节点。
将所有这些层的节点数加起来,我们得到:
总节点数 = 1 + 2 + 2^2 + … + 2^(n-1)
这是一个等比数列,其求和公式为:
S = a * (1 - r^n) / (1 - r)
其中,a 是首项,r 是公比,n 是项数。在这里,a = 1, r = 2, n = n-1(因为是从第 2 层开始计算的)。
代入公式得到:
总节点数 = 1 * (1 - 2^(n-1)) / (1 - 2)
= 2^n - 1
因此,一棵 n 层的二叉树最多有 2^n - 1 个节点。
6、提出“存储程序”的计算机工作原理的是( )
A. 克劳德·香农
B. 戈登·摩尔
C. 查尔斯·巴比奇
D. 冯·诺依曼
正确答案:D
冯·诺依曼(John von Neumann)是20世纪最重要的数学家之一,他在计算机科学、数学、物理学、经济学和统计学等多个领域都有卓越的贡献。1945年,冯·诺依曼提出了存储程序的概念,即计算机应该包含存储器,并且程序本身应该存储在计算机的存储器中。这一思想奠定了现代电子计算机的基本结构,即冯·诺依曼结构,也被称为普林斯顿结构。
克劳德·香农(Claude Shannon)是信息论的创始人,他主要贡献在于信息论和通信理论。
戈登·摩尔(Gordon Moore)是英特尔公司的创始人之一,他提出了摩尔定律,预测了在一个芯片上集成的晶体管数量将每两年翻一倍。
查尔斯·巴比奇(Charles Babbage)是计算机的先驱之一,他设计了差分机和分析机,这是最早的机械计算机之一。
7、设 X、Y、Z 分别代表三进制下的一位数字,若等式 XY + ZX = XYX 在三进制下成立, 那么同样在三进制下,等式 XY * ZX = ( )也成立
A. YXZ
B. ZXY
C. XYZ
D. XZY
正确答案:B
三进制的三个数字分别为0,1,2。X,Y,Z使用这三个数字测试
X Y
Z X
——
X Y X
上式是两位数加两位数,可知有进位,可以知进位X的值为1,不可能为2。同时由Y+X=X可知,只有0+1=1,0+2=2符号情况。那么Y是0。根据X+Z=Y知,Z应该是2。计算XYZX,则计算1021。结果为210,对应的是ZXY。
8、Pascal 语言、 C 语言和 C++ 语言都属于( )
A. 面向对象语言
B. 脚本语言
C. 解释性语言
D. 编译性语言
正确答案:D
面向对象语言:Java,C++,C#,python
脚本语言: PHP, VBScript, HTML, PowerShell,JavaScript
解释性语言: 程序不需要编译,程序在运行时才翻译成机器语言,每执 行一次都要翻译一次.,例如JavaScript、VBScript、Perl、Python、Ruby、MATLAB
编译性语言: 程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。C/C++、Pascal/Object Pascal(Delphi)
Pascal是一种编译型、静态类型检查、强类型的编程语言,支持结构化编程、词汇变量作用域和递归等功能。
C语言也是一种编译型语言,它提供了低级别的存取权限,并且要求程序员管理所有的内存细节。
C++是C语言的扩展,支持面向对象编程,包括类、封装、继承和多态等概念。它也是编译型语言。
9、前缀表达式“ + 3 * 2 + 5 12 ”的值是( )
A. 23
B. 25
C. 37
D. 65
正确答案:C
前缀转中缀表达式方法:①画二叉树,②堆栈法,③加括号法。
主要介绍第二种。规则如下:
从右至左扫描表达式,如果遇到操作数,则入栈。
如果遇到操作符,则将栈顶元素弹出(后扫面的数字位于表达式前面),并和操作符结合写成表达式,作为中缀表达式再次入栈。
如果遇到的操作符优先级大于已存在表达式的最后执行操作符的优先级,则将已存在的表达式加上括号。
注意计算顺序,一般栈顶的元素在前。
操作符 | 说明 | 栈内情况(栈底 -> 栈顶) | |
---|---|---|---|
12 | 数字,入栈 | 12 | |
5 | 数字,入栈 | 12 | 5 |
+ | 操作符,计算栈顶两个元素,并把计算结果再次入栈。 | 5+12 | |
2 | 数字,入栈 | 5+12 | 2 |
* | 操作符,计算栈顶两个元素。并把计算结果再次入栈。栈顶元素在前,所以要加括号。 | 2*(5+12) | |
3 | 数字,入栈 | 2*(5+12) | 3 |
+ | 操作符,计算栈顶两个元素。并把计算结果再次入栈。 | 3 +2*(5+12) | |
空 | 没有操作符,输出 |
10、主存储器的存取速度比中央处理器( CPU)的工作速度慢得多, 从而使得后者的效率受 到影响。 而根据局部性原理, CPU所访问的存储单元通常都趋于聚集在一个较小的连续区域 中。于是,为了提高系统整体的执行效率,在 CPU中引入了( )。
A. 寄存器
B. 高速缓存
C. 闪存
D. 外存
正确答案:B
高速缓存是一种小型的、快速的存储设备,位于 CPU 和主存储器之间。它的作用是存储 CPU 访问最频繁的数据和指令,从而减少 CPU 访问主存储器的次数,因为主存储器的存取速度相对较慢。由于 CPU 所访问的存储单元通常都趋于聚集在一个较小的连续区域中(这是局部性原理的体现),因此高速缓存能够显著提高 CPU 的效率。
当 CPU 需要访问数据时,它首先会检查高速缓存中是否有所需的数据。如果数据在高速缓存中(这称为命中),则 CPU 可以直接从高速缓存中读取数据,速度非常快。如果数据不在高速缓存中(这称为未命中),则 CPU 需要从主存储器中读取数据,并将该数据及其相邻的数据块存储在高速缓存中,以备将来使用。
A项:寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。
B项:高速缓冲存储器是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多(属于高速缓存), 接近于CPU的速度。
C项:闪存是技术人员保存安装BIOS等计算机基础设置的地方,也可以是数码相机内的数据存储卡,不属于CPU的范畴
D项:外存是是指除计算机内存及CPU缓存以外的储存器,此类储存器一般断电后仍然能保存数据。常见的外存储器有硬盘、软盘、光盘、U盘等。
11、一个字长为 8 位的整数的补码是 11111001 ,则它的原码是( )
A. 00000111
B. 01111001
C. 11111001
D. 10000111
正确答案:D
一个正数的原码=反码=补码,一个负数的原码=补码取反加1,本题“1111 1001”首位数是“1”,则这个数是负数,则原码=补码取反码加1=1000 0110=1000 0111,故选D
12、基于比较的排序时间复杂度的下限是( ),其中 n 表示待排序的元素个数。
A . O ( n ) A. O(n) A.O(n)
B . O ( n l o g n ) B. O(nlogn) B.O(nlogn)
C . O ( l o g n ) C. O(logn) C.O(logn)
D . O ( n 2 ) D. O(n^2) D.O(n2)
正确答案:B
对于排序的按是否比较分为比较类和非比较类。
比较类有:交换(冒泡、快排),插入(简单插入、希尔),选择(简单选择,堆排),归并(二路归并,多路归并)
非比较类:计数排序,桶排序,基数排序
对于n个待排序元抄素,在未比较时,可能的正确结果有n!种。这些序列都是右面这个二叉树中某一个从根到叶子的路径。那么基于比较的排序的时间复杂度就是这颗数的深度H(N)。而且很容易知道叶子节点的个数就是N的排列 N!,那么我们就有
比属较次数至少为log(N!)=O(NlogN)(斯特林公式)。
注:斯特林公式(Stirling’s approximation)是一条用来取n的阶乘的近似值的数学公式
13、一个自然数在十进制下有 n 位,则它在二进制下的位数与( )最接近。
A . 5 n A. 5n A.5n
B . n ∗ l o g 2 10 B. n*log_210 B.n∗log210
C . 10 ∗ l o g 2 n C. 10*log_2n C.10∗log2n
D . 1 0 n ∗ l o g 2 n D. 10^n*log_2n D.10n∗log2n
正确答案:B
十进制转二进制一般使用除二取余法。
可以举例尝试,
两位数37,转换成二进制是100101
A项:52=10
B:项 nlog 210≈6.64
C项: 10log 2n=10
D项:10 nlog 2n =100
再如:三位数176,转换成二进制是10110000
A项:53=15
B:项:nlog 210≈10.53
C项: 10log 2n≈15.8
D项:10 nlog 2n =1584.96
没有好的证明方法,通过举例可以看出,B项是最接近的。
14、在下列 HTML 语句中,可以正确产生一个指向 NOI 官方网站的超链接的是( )
A. <a url="http://www.noi.cn">欢迎访问 NOI 网站</a>
B. <a href="http://www.noi.cn">欢迎访问 NOI 网站</a>
C. <a>http://www.noi.cn</a>
D. <a name="http://www.noi.cn">欢迎访问 NOI 网站</a>
正确答案:B
在HTML 中超链接的标签是 a。a的用法是 < a > “要显示的内容”< /a > 。href表示这个超链接指向的地址是什么地方。
在 HTML 中,要创建一个超链接,应使用 < a > 标签,并使用 href 属性来指定链接的目标地址。所以,正确的选项是:
B. < a href=“http://www.noi.cn” >欢迎访问 NOI 网站
这个语句会创建一个指向 http://www.noi.cn 的超链接,链接的文本是“欢迎访问 NOI 网站”。
A 选项中的 url 属性是不正确的,应该是 href。
C 选项虽然包含了目标地址,但没有用 href 属性来指定,因此不会创建超链接。
D 选项中的 name 属性用于指定锚点(页面内的跳转点),而不是用于指定超链接的目标地址。
15、元素 R1、R2、R3、R4、R5 入栈的顺序为 R1、R2、R3、R4、R5。如果第 1 个出栈的 是 R3,那么第 5 个出栈的不可能是( )
A. R1
B. R2
C. R4
D. R5
正确答案:B
入栈顺序:R1,R2, R3。因为R3第一个出栈,所以R4和R5此时不能入栈,必须等R3先出栈。当R3出栈后,栈内还剩R1,R2,观察可以看出,R2在R1的上面,不论怎样R2一定比R1先出栈,最坏的情况是R1最后出栈,所以选B
16、双向链表中有两个指针域 llink 和 rlink ,分别指向该结点的前驱及后继。 设 p 指向 链表中的一个结点,它的左右结点均非空。现要求删除结点 p ,则下面语句序列中错误的是 ( )
A. p->rlink->llink = p->rlink;p->llink->rlink = p->llink; delete p;
B. p->llink->rlink = p->rlink; p->rlink->llink = p->llink; delete p;
C. p->rlink->llink = p->llink;p->rlink->llink->rlink = p->rlink; delete p;
D. p->llink->rlink = p->rlink;p->llink->rlink->llink = p->llink; delete p;
正确答案:A
步骤:(思路:断开连接,再删除)
第一步:找到即将被删除的节点 p
第二步:将 p 的前驱的后继指向 p 的后继,即 p->rlink->llink = p->rlink;
第三步:将 p 的后继的前驱指向 p 的前驱,即 p->llink->rlink = p->llink;
第四步:删除节点 p 即 delete p;
17、一棵二叉树的前序遍历序列是 ABCDEFG,后序遍历序列是 CBFEGDA,则根结点的左 子树的结点个数可能是( )。
A. 2
B. 3
C. 4
D. 5
正确答案:A
已知前序遍历和后续遍历不能确定唯一一棵二叉树
先序遍历是先访问根结点,再从左到右按照先序思想遍历各个子树
后序遍历是从左到右遍历各个子树,再访问跟结点。
可以画出如上图所示的图像。
18、关于拓扑排序,下面说法正确的是( )
A. 所有连通的有向图都可以实现拓扑排序
B. 对同一个图而言,拓扑排序的结果是唯一的
C. 拓扑排序中入度为 0 的结点总会排在入度大于 0 的结点的前面
D. 拓扑排序结果序列中的第一个结点一定是入度为 0 的点
正确答案:D
拓扑排序:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
A. 所有连通的有向图都可以实现拓扑排序
这个说法是错误的。拓扑排序只能应用于有向无环图(DAG)。如果图中存在环,则无法进行拓扑排序,因为环中的顶点没有明确的排序顺序。
B. 对同一个图而言,拓扑排序的结果是唯一的
这个说法也是错误的。对同一个DAG而言,拓扑排序的结果可能不是唯一的。例如,在一个简单的DAG中,如果有多个顶点的入度都是0,这些顶点可以作为拓扑排序结果的开始。
C. 可以举例说明,如
1->3
2
也是正确的拓扑排序,当时3却在2之前
D. 拓扑排序结果序列中的第一个结点一定是入度为 0 的点
这个说法也是正确的。由于拓扑排序是从入度为0的顶点开始的,所以结果序列中的第一个结点必然是入度为0的点。
19、完全二叉树的顺序存储方案,是指将完全二叉树的结点从上至下、从左至右依次存放 到一个顺序结构的数组中。假定根结点存放在数组的 1 号位置,则第 k 号结点的父结点如 果存在的话,应当存放在数组的( )号位置。
A. 2k
B. 2k+1
C. ⌊k/2⌋
D. ⌊(k+1)/2⌋
正确答案:C
在完全二叉树的顺序存储中,我们通常使用一个数组来存储所有的节点。在这个存储方案中,根节点通常被放置在数组的第1个位置(索引为1)。对于任何一个在数组中的节点,如果它的索引是k,我们可以使用下面的方法来找到其父节点的索引:
由于根节点在索引1的位置,它的左子节点(如果存在)会在索引2的位置,右子节点会在索引3的位置,依此类推。因此,对于任何索引为k的节点,它的父节点(如果存在)的索引会是⌊k/2⌋。
这是因为要找到父节点,我们需要将k除以2并向下取整。这是因为父节点总是在其子节点之前(在数组中索引更小)。
对于k=1,则k为根结点,无父母结点。
对于k>1,可分两种情况讨论。
1.设完全二叉树第j层的百第一个结点的编号为i,则i=2的j-1次方。其度左孩子必为第j+1层的第一个结点。其编号为2的j次方,这和2i相等。右孩子为j+1层的第二个结点,结点编号为2i+1。所以,在这种情况下,左孩子编号为2i,右孩子编号为2i+1,父母结点编号为i,2i/2,(i2+1)/2取整均为i。
2.设完全二叉树第回j层上的某个结点编号为i,编号为i+1的结点为编号为i的结点的右兄弟或者堂兄弟,若它有左孩子,编号为2i+2=2(i+1),或它有右孩子,编号为2i+3=2(i+1)+1。在答这种情况下,2(i+1)/2,[2(i+1)+1]/2取整均为i+1。
实际上,稍微寻找一下规律便知道,当k是偶数,父结点是k/2, 当k是奇数,父结点是(k-1)/2,综合一下就是k/2 向下取整。
20、全国青少年信息学奥林匹克系列活动的主办单位是( )
A. 教育部
B. 科技部
C. 共青团中央
D. 中国计算机学会
正确答案:D
关于主办单位,我们逐项考察:
A. 教育部 - 主要负责全国的教育政策和管理工作,但它不直接组织特定的专业性活动,如信息学奥林匹克。
B. 科技部 - 负责全国的科技发展和研究,但也不是NOI系列活动的直接主办单位。
C. 共青团中央 - 是中国共青团的组织管理机构,主要负责青少年的思想政治工作和活动组织,虽然它可能支持或协办一些青少年活动,但并不是NOI系列活动的主办单位。
D. 中国计算机学会(CCF) - 是一个专注于计算机科学和相关领域的学术组织。CCF经常组织和参与各种计算机科学和教育活动,包括青少年信息学奥林匹克系列活动。
二. 问题求解(共2题,每题5分,共计10分)
1、LZW 编码是一种自适应词典编码。在编码的过程中,开始时只有一部基础构造元素的编 码词典, 如果在编码的过程中遇到一个新的词条, 则该词条及一个新的编码会被追加到词典 中,并用于后继信息的编码。
举例说明,考虑一个待编码的信息串: “xyx yy yy xyx” 。初始词典只有 3 个条目, 第一个为 x,编码为 1;第二个为 y,编码为 2 ;第三个为空格,编码为 3;于是串 “xyx” 的编码为 1-2-1 (其中 – 为编码分隔符) ,加上后面的一个空格就是 1-2-1-3 。但由于有了 一个空格, 我们就知道前面的 “xyx” 是一个单词, 而由于该单词没有在词典中, 我们就可以 自适应的把这个词条添加到词典里,编码为 4 ,然后按照新的词典对后继信息进行编码,以 此类推。于是,最后得到编码: 1-2-1-3-2-2-3-5-3-4 。
现在已知初始词典的 3 个条目如上述,则信息串 “yyxy xx yyxy xyx xx xyx” 的 编码是_________。__
正确答案:2-2-1-2-3-1-1-3-4-3-1-2-1-3-5-3-6
LZW是一种编码是一种字典编码。其基本原理是把没见过的按顺序编号都放入字典中。
题目中样例的解释
信息串 | 字典里的编号 | 说明 |
---|---|---|
x | 1 | 第一个字符,编号为1 |
y | 2 | 第二个字符,编号为2 |
x | 1 | x出现过一次,查找编号,编号为1 |
空格 | 3 | 空格字符,编号为3 |
xyx | 4 | 遇到空格,前面的作为一个单词,编到字典里 |
y | 2 | y出现过一次,查找编号,编号为2 |
y | 2 | y出现过一次,查找编号,编号为2 |
空格 | 3 | 空格字符,编号为3 |
yy | 5 | 遇到空格,前面的作为一个单词,编到字典里 |
y | 2 | 第二个字符,编号为2 |
y | 2 | 第二个字符,编号为2 |
空格 | 3 | 空格字符,编号为3 |
yy | 5 | 遇到空格,前面的作为一个单词,编到字典里 |
x | 1 | 第一个字符,编号为1 |
y | 2 | 第二个字符,编号为2 |
x | 1 | x出现过一次,查找编号,编号为1 |
空格 | 3 | 空格字符,编号为3 |
xyx | 4 | 遇到空格,前面的作为一个单词,编到字典里 |
样例的结果为1-2-1-3-2-2-3-5-3-4,也就是说,单词第一次出现只编号放入到字典中,第二次出现直接显示编号即可。而且最后一个空格不输出
yyxy xx yyxy xyx xx xyx
信息串 | 字典里的编号 | 说明 | 结果序列 |
---|---|---|---|
y | 2 | 遵循题目要求,y初始为2 | 2 |
y | 2 | y为2 | 2-2 |
x | 1 | x初始为1 | 2-2-1 |
y | 2 | 2-2-1-2 | |
空格 | 3 | 遇到空格,空格为3 | 2-2-1-2-3 |
yyxy | 4 | 按照顺序编号,则yyxy这个单词为4 (只编号,不显示) | 2-2-1-2-3 |
x | 1 | 出现过 | 2-2-1-2-3-1 |
x | 1 | 出现过 | 2-2-1-2-3-1-1 |
空格 | 3 | 遇到空格,空格为3 | 2-2-1-2-3-1-1-3 |
xx | 5 | xx做为一个单词,没有出现过,编号为5(只编号不,显示) | 2-2-1-2-3-1-1-3 |
yyxy | 4 | 出现过 | 实际上是单词的一部分 |
空格 | 3 | 空格,前方是一个单词,是4,然后再加上空格3 | 2-2-1-2-3-1-1-3-4-3 |
x | 1 | 出现过 | 2-2-1-2-3-1-1-3-4-3-1 |
y | 2 | 出现过 | 2-2-1-2-3-1-1-3-4-3-1-2 |
x | 1 | 出现过 | 2-2-1-2-3-1-1-3-4-3-1-2-1 |
空格 | 3 | 遇到空格,空格为3 | 2-2-1-2-3-1-1-3-4-3-1-2-1-3 |
xyx | 6 | xyx是一个新单词,编号为6,(只编号,不显示) | 2-2-1-2-3-1-1-3-4-3-1-2-1-3 |
xx | 5 | 出现过 | 2-2-1-2-3-1-1-3-4-3-1-2-1-3-5 |
空格 | 3 | 遇到空格,空格为3 | 2-2-1-2-3-1-1-3-4-3-1-2-1-3-5-3 |
xyx | 6 | 出现过 | 2-2-1-2-3-1-1-3-4-3-1-2-1-3-5-3-6 |
空格 | 3 | 最后一个不输出 | 2-2-1-2-3-1-1-3-4-3-1-2-1-3-5-3-6 |
2、队列快照是指在某一时刻队列中的元素组成的有序序列。例如,当元素 1 、2 、3 入队, 元素 1 出队后, 此刻的队列快照是 “2 3” 。当元素 2、3 也出队后,队列快照是 “” ,即为空。 现有 3 个正整数元素依次入队、出队。已知它们的和为 8,则共有 _______ 种可能的不 同的队列快照(不同队列的相同快照只计一次)。例如, “5 1″ 、”4 2 2″ 、”” 都是可能 的队列快照;而 “7” 不是可能的队列快照,因为剩下的 2 个正整数的和不可能是 1
正确答案:49
三个正整数和为8,考虑不同的情况:
116 | 125 | 134 | 143 | 152 | 161 |
---|---|---|---|---|---|
215 | 224 | 232 | 242 | 251 | |
314 | 323 | 332 | 341 | ||
413 | 422 | 431 | |||
512 | 521 | ||||
611 |
可以看出只有21个快照,然后把队头去掉
16 | 25 | 34 | 43 | 52 | 61 |
---|---|---|---|---|---|
15 | 24 | 32 | 42 | 51 | |
14 | 23 | 32 | 41 | ||
13 | 22 | 31 | |||
12 | 21 | ||||
11 |
可以看出,上面的队列一共有21个。再次去掉队头,
6 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|
5 | 4 | 2 | 2 | 1 | |
4 | 3 | 2 | 1 | ||
3 | 2 | 1 | |||
2 | 1 | ||||
1 |
去掉重复的后,还剩6个。再加上一个空最后是21+21+6+1=49
三. 阅读程序写结果(共4题,每题8分,共计32分)
1、阅读程序写结果:
#include <iostream>
using namespace std;
void swap(int & a, int & b){
int t;
t = a;
a = b;
b = t;
}
int main(){
int a1, a2, a3, x;
cin>>a1>>a2>>a3;
if (a1 > a2)
swap(a1, a2);
if (a2 > a3)
swap(a2, a3);
if (a1 > a2)
swap(a1, a2);
cin>>x;
if (x < a2)
if (x < a1)
cout<<x<<' '<<a1<<' '<<a2<<' '<<a3<<endl;
else
cout<<a1<<' '<<x<<' '<<a2<<' '<<a3<<endl;
else
if (x < a3)
cout<<a1<<' '<<a2<<' '<<x<<' '<<a3<<endl;
else
cout<<a1<<' '<<a2<<' '<<a3<<' '<<x<<endl;
return 0;
}
输入:
91 2 20
77
输出:________
正确答案:2 20 77 91
swap函数的目的是交换两个数,注意,&表示取地址符,也就是这个操作会把main函数中的值一同交换。
输出值a1=91, a2=2 , a3 =20,x=77
第一个if (a1 > a2),成立,更新值为: a1=2, a2=91 , a3 =20,x=77
第二个if (a2 > a3),成立,更新值为: a1=2, a2=20 , a3 =91,x=77
第三个if (a1 > a2),不成立,无更新,结果为: a1=2, a2=20 , a3 =91,x=77
读入x后,判断if(x < a2),不成立,执行else语句,
继续判断if (x < a3) 成立,则按顺序输出a1,a2, x, a3
2、阅读程序写结果:
#include <iostream>
using namespace std;
int rSum(int j){
int sum = 0;
while (j != 0) {
sum = sum * 10 + (j % 10);
j = j / 10;
}
return sum;
}
int main(){
int n, m, i;
cin>>n>>m;
for (i = n; i < m; i++)
if (i == rSum(i))
cout<<i<<' ';
return 0;
}
输入:90 120
输出:____
正确答案:99 101 111
可以看函数内容先找规律,sum = sum * 10 + (j % 10);是把一个数的各位取出且把上个过程中的数扩大10倍加起来,然后缩小10倍继续求。这是一个数求逆序的步骤。
或者按如下过程逐步求规律。
观察主函数中for循环 ,是从90到120,期间的条件满足一个rSum条件则输出i。
然后观察rSum函数。从90传入进去查看过程
参数 | while (j != 0) | sum = sum * 10 + (j % 10) | j = j / 10 | return sum |
---|---|---|---|---|
90 | 成立 | 0 | 9 | |
成立(j为9) | 9 | 0 | 9 | |
91 | 成立 | 1 | 9 | |
成立(j为9) | 19 | 0 | 19 | |
92 | 成立 | 2 | 9 | |
成立(j为9) | 29 | 0 | 29 |
可以猜想后面的值分别是39,49,59,69,79,89,99在这些数字中,只有99是符符合要求的,所以第一个数输出99。然后看三位数的变化。
参数 | while (j != 0) | sum = sum * 10 + (j % 10) | j = j / 10 | return sum |
---|---|---|---|---|
100 | 成立 | 0 | 10 | |
成立(j为10) | 0 | 1 | ||
成立(j为1) | 1 | 0 | 1 | |
101 | 成立 | 1 | 10 | |
成立(j为10) | 10 | 1 | ||
成立(j为1) | 101 | 0 | 101 | |
102 | 成立 | 2 | 10 | |
成立(j为10) | 20 | 1 | ||
成立(j为1) | 201 | 0 | 201 | |
103 | 成立 | 3 | 10 | |
成立(j为10) | 30 | 1 | ||
成立(j为1) | 301 | 0 | 301 |
从上方可以看出函数的规律也就是输入一个数,然后把这个数倒序输出。主函数中等号表示哪些数倒叙输出后和原来一样,此题实际上是在求解回文数。
3、阅读程序写结果
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
char m1, m2;
int i;
getline(cin, s);
m1 = ' ';
m2 = ' ';
for (i = 0; i < s.length(); i++)
if (s[i] > m1) {
m2 = m1;
m1 = s[i];
}
else if (s[i] > m2)
m2 = s[i];
cout<<int(m1)<<' '<<int(m2)<<endl;
return 0;
}
输入:Expo 2010 Shanghai China
输出:_________
提示:
字符 | 空格 | ‘0’ | ‘A’ | ’a’ |
---|---|---|---|---|
ASCII码 | 32 | 48 | 65 | 97 |
正确答案:120 112
先看程序代码。有两个if的比较,在比较过程中都是把比较大的值赋值出来,且第一个if有两个赋值,分别是把较大的赋值给m2,较小的赋值给m1,那么每次循环完毕后,m2总是保持最大的值,m1总是保持最小的值。然后m1和m2的值即可。
或者也可以按照以下步骤求解规律。
i | s[i] | 比较过程 | m1 | m2 |
---|---|---|---|---|
0 | E | if成立 | E | 空 |
1 | x | if成立 | x | E |
2 | p | else成立 | x | p |
3 | o | 没有任何成立 | x | p |
4 | 空格 | 没有任何成立 | x | p |
5 | 2 | 没有任何成立 | x | p |
后面的任何字母都没有x和p大,所以m1保留x。m2保留p,最后输出m1和m2的值即可
4、阅读程序写结果
#include <iostream>
using namespace std;
const int NUM = 5;
int r(int n){
int i;
if (n <= NUM)
return n;
for (i = 1; i <= NUM; i++)
if (r(n - i) < 0)
return i;
return -1;
}
int main(){
int n;
cin>>n;
cout<<r(n)<<endl;
return 0;
}
(1)
输入:7
输出:_________(4分)
(2)
输入:16
输出:_________(4分)
正确答案:
1
4
一个递归
当n小于5时,返回n,当n大于5时,进入for循环同时递归。一定要注意for循环和递归的关系。为了方便期间,先把前面的几个求出,实际上参考表格法,可以先倒序把这些值求出,在计算过程中,直接寻找即可。如果从正面开始直接循环和递归的话很容易理不清层次而出错。
r(1)=1, r(2)=2, r(3)=3, r(4)=4, r(5)=5,
r(6)的值,首先进入到for循环中,r(6)因为for循环一个没成立,最后的结果为-1
r(7)的值,也是进入到for循环中,因为i=1时成立,所以r(7)=1。
r(8)的值,也是进入到for循环中, 因为i=2时成立,所以r(7)=2。
可以猜想r(9)=3,r(10)=4,r(11)=5。当r(12)时,for循环中的if (r(n – i) < 0) 没有任何一个成立,r(12)=-1
至此可以看出,整个数据是1,2,3,4,5,-1的循环,所以r(16)=4。
四. 完善程序 (前4空,每空2.5分,后6空,每空3分,共28分)
1、(哥德巴赫猜想) 哥德巴赫猜想是指,任一大于 2 的偶数都可写成两个质数之和。迄今 为止, 这仍然是一个著名的世界难题,被誉为数学王冠上的明珠。 试编写程序,验证任一大 于 2 且不超过 n 的偶数都能写成两个质数之和。
#include <iostream>
using namespace std;
int main(){
const int SIZE = 1000;
int n, r, p[SIZE], i, j, k, ans;
bool tmp;
cin>>n;
r = 1;
p[1] = 2;
for (i = 3; i <= n; i++) {
[ ① ];
for (j = 1; j <= r; j++)
if (i % [ ② ] == 0) {
tmp = false;
break;
}
if (tmp) {
r++;
[ ③ ] ;
}
}
ans = 0;
for (i = 2; i <= n / 2; i++) {
tmp = false;
for (j = 1; j <= r; j++)
for (k = j; k <= r; k++)
if (i + i == [ ④ ] ) {
tmp = true;
break;
}
if (tmp)
ans++;
}
cout<<ans<<endl;
return 0;
}
若输入 n 为 2010,则输出[ ⑤ ]时表示验证成功,即大于 2 且不超过 2010 的偶数都满足哥德巴赫猜想。
正确答案:
1.tmp=1 / tmp=true
2.p[j]
3.p[r]=I
4.p[j]+p[k] / p[k]+p[j]
5.1004
验证哥德巴赫猜想,题目中有判断质数的部分, 查看程序第一个for循环是求n以内所有的质数,并把结果放到p数组中,第二个for循环是验证大于2的不超过n的偶数能否写成两个质数之和。
①【 tmp=true 】tmp初始化,因为下方出现了修改tmp的值,所以tmp为true
②【 p[j] 】判断能否整除,如果能则修改tmp的值,并直接结束掉此循环。所以这个地方是判断i是不是质数,p数组是用来存放质数的数组。
③【 p[r]=i 】r++,是准备存取下一个质数到p数组中,所以应该是p[r]=i;
④ 【 p[j]+p[k] 】此处是验证哥德巴赫猜想,大于2 的偶数可以表示成两个质数之和,所以此处应该填两个质数之和,根据上层两个for循环推出此处应该是质数的穷举,p数组的p[j]和p[k]分别穷举不同的质数组合。
⑤【 1004 】是为了表示一共有多少个需要输出。需要注意把2排除在外,所以一共2008个偶数,那么最后输出1004,因为原题范围是n/2。
2、(过河问题) 在一个月黑风高的夜晚,有一群人在河的右岸,想通过唯一的一根独木桥 走到河的左岸。 在这伸手不见五指的黑夜里, 过桥时必须借助灯光来照明, 很不幸的是,他 们只有一盏灯。 另外,独木桥上最多承受两个人同时经过,否则将会坍塌。 每个人单独过桥 都需要一定的时间,不同的人需要的时间可能不同。两个人一起过桥时,由于只有一盏灯,所以需要的时间是较慢的那个人单独过桥时所花的时间。现输入 n( 2≤n≤100)和这 n 个 人单独过桥时需要的时间,请计算总共最少需要多少时间,他们才能全部到达河的左岸。 例如,有 3 个人甲、乙、丙,他们单独过桥的时间分别为 1、 2、4,则总共最少需要 的时间为 7 。具体方法是:甲、乙一起过桥到河的左岸,甲单独回到河的右岸将灯带回,然 后甲、丙再一起过桥到河的左岸,总时间为 2+1+4=7。
#include <iostream>
using namespace std;
const int SIZE = 100;
const int INFINITY = 10000;
const bool LEFT = true;
const bool RIGHT = false;
const bool LEFT_TO_RIGHT = true;
const bool RIGHT_TO_LEFT = false;
int n, hour[SIZE];
bool pos[SIZE];
int max(int a, int b){
if (a > b)
return a;
else
return b;
}
int go(bool stage){
int i, j, num, tmp, ans;
if (stage == RIGHT_TO_LEFT) {
num = 0;
ans = 0;
for (i = 1; i <= n; i++)
if (pos[i] == RIGHT) {
num++;
if (hour[i] > ans)
ans = hour[i];
}
if ([ ① ])
return ans;
ans = INFINITY;
for (i = 1; i <= n - 1; i++)
if (pos[i] == RIGHT)
for (j = i + 1; j <= n; j++)
if (pos[j] == RIGHT) {
pos[i] = LEFT;
pos[j] = LEFT;
tmp = max(hour[i], hour[j]) +[ ② ];
if (tmp < ans)
ans = tmp;
pos[i] = RIGHT;
pos[j] = RIGHT;
}
return ans;
}
if (stage == LEFT_TO_RIGHT) {
ans = INFINITY;
for (i = 1; i <= n; i++)
if ([ ③ ]) {
pos[i] = RIGHT;
tmp =[ ④ ];
if (tmp < ans)
ans = tmp;
[ ⑤ ];
}
return ans;
}
return 0;
}
int main()
{
int i;
cin>>n;
for (i = 1; i <=n; i++) {
cin>>hour[i];
pos[i] = RIGHT;
}
cout<<go(RIGHT_TO_LEFT)<<endl;
return 0;
}
正确答案:
1.num <= 2 / num < 3 / num == 2
2.go(LEFT_TO_RIGHT)
3.pos[i] == LEFT / LEFT == pos[i]
4.hour[i] + go(RIGHT_TO_LEFT) / go(RIGHT_TO_LEFT) + hour[i]
5.pos[i] = LEFT
过河问题,贪心算法的一个应用,需要明确贪心策略。
其中:
max函数是返回两个数中较大的一个。
go函数难点,从左向右走和从右向左走分成。
hour数组存放的是每个人的时间。
根据main函数中pos和hour同步赋值,推算pos数组是用来记录每个元素i所处的位置是left还是right。
ans:根据if (hour[i] > ans) ans = hour[i]; 猜想ans是从右向左最长的过桥时间。
num:一般为数字,题目中只有 if (pos[i] == RIGHT) num++;与num有关,解释为有一个人在RIGHT侧,num就加一,推论num是表示在右侧的人数。
本小题中, LEFT 可用 true 代替, LEFT_TO_RIGHT 可用 true 代替, RIGHT_TO_LEFT 可用 false 代替。
①【 num<=2 】【解析】根据go函数的内容,这部分是从右向左,下方是return ,也就是询问if满足什么条件时可以结束程序,当右边的人小于等于2时,表示右边人全部已经到左边了。
②【 go(LEFT_TO_RIGHT 】是一个普通的回溯,此次过桥的时间+未知的后续的过桥时间
③【 pos[i] == LEFT 】 跟上方类似,上一处判断是ROGHT侧,这里判断在LEFT侧。
④ 【 hour[i] + go(RIGHT_TO_LEFT) 】 此处再次递归
⑤【 pos[i] = LEFT 】 将状态改回
【附注】
题目中的例子太弱,举原例说明。
假如甲乙丙丁4人单独过河所需时间分别是1,2,5,8。那么在这种情况下,
第一种方案:甲乙先过去(2分钟),甲回来(1分钟),甲丙过去(5分钟),甲回来(1分钟),甲丁过去(8分钟)。总共17分钟
第二种方案:甲乙过去(2分钟),甲回来(1分钟),丙丁过去(8分钟),乙回来(2分钟),甲乙过去(2分钟)。总共15分钟。
可以看出这种方法的关键点,是让两个最慢的人同时过桥。
那么看下一种例子,假如甲乙丙丁过河时间分别是1,4,5,8
第一种方案:甲乙过去(4分钟),甲回来(1分钟),甲丙过去(5分钟),甲回来(1分钟),甲丁过去(8分钟)总共19分钟。
第二种方案:甲乙过去(4分钟),甲回来(1分钟),丙丁过去(8分钟),乙回来(4分钟),甲乙过去(4分钟),总共21分钟。
这次,两个最慢的人反而更慢了。上面两种方案的差异,是次快的人要不要也传递灯。
总结两种方式:
最快的(即所用时间t[0])和次快的过河,然后最快的回来,再次慢的和最慢的过河,然后次快的回来
最快的和最慢的过河,然后最快的回来,再最快的和次慢的过河,然后最快的回来。
那么,可以继续总结下思路:
首先明确,每次要送回去灯,这就要求送灯的人必须是他们中最快的。
既然送回灯的人必须是其中最快的,那么这个人一定是最后一批到达对面的人。
两个速度较慢的人一起过河,可以节省时间。(例如:甲乙过河各需100,两人同时过河只需100,如果分别过河需要200)
如果速度最快的两个人速度优势不明显,比如次快和最慢的就会出现时间反而变慢了。这个时候,可以用最快的人传递等等,方法是最快的和最慢的过去回来在和次慢一起过去
重复上面步骤,直到所有人都过去。
此题并未按照上述算法严格执行,此解释只做拓展。