2019年CSP提高组初赛答案及解析
原网址https://www.bilibili.com/read/cv3833576/
一、单项选择题
1. 若有定义:int a=7; float x=2.5,y=4.7;则表达式x+a%3*(int)(x+y)%2的值是:( )
A.0.000000 B.2.750000 C.2.500000 D.3.500000
解析:D。基础,考察数据类型和算术优先级。
2. 下列属于图像文件格式的有( )
A.WMV B.MPEG C.JPEG D.AVI
解析:C。计算机基础知识,其他三个是视频格式。
3. 二进制数11 1011 1001 0111 和 01 0110 1110 1011 进行逻辑或运算的结果是( )。
A.11 1111 1111 1101 B.11 1111 1111 1101 C.10 1111 1111 1111 D.11 1111 1111 1111
解析:D。位运算基本知识,两位上有一位为1的时候结果就为1。0|0=0;1|0=1;0|1=1;1|1=1。
4. 编译器的功能是( )
A.将源程序重新组合
B.将一种语言(通常是高级语言)翻译成另一种语言(通常是低级语言)
C.将低级语言翻译成高级语言
D.将一种编程语言翻译成自然语言
B。编译器是将高级语言(如C++)编译成计算机能够理解的二进制0和1(机器语言、低级语言)
5. 设变量x为float型且已赋值,则以下语句中能将x中的数值保留到小数点后两位,并将第三位四舍五入的是( )
A.X=(x*100+0.5)/100.0;
B.x=(int)(x*100+0.5)/100.0;
C.x=(x/100+0.5)*100.0;
D.x=x*100+0.5/100.0;
B。主要考察强制类型转换,加0.5再转成int是四舍五入常用操作。
6. 由数字1,1,2,4,8,8所组成的不同的4位数的个数是( )
A.104 B.102 C.98 D.100
B。排列组合问题,也可以用穷举算。 若由4种不同数字即1、2、4、8有A(4,4)=24种; 若有且只有2个数一样,共有1124;1128;1148;1288;1488;2488六类,共6*A(4,4)/A(2,2)=72种; 若1、1、8、8组合有A(4,4)/(A(2,2)*2)=6种;
7. 排序的算法很多,若按排序的稳定性和不稳定性分类,则( )是不稳定排序。
A.冒泡排序 B.直接插入排序 C.快速排序 D.归并排序
C。排序的稳定性特点是排序完成后,之前相同的元素排序不会改变。快速排序在排序时在交换中间元素时可能会打乱顺序。如3、1、1、2、1、6、7、8、9,在一开始3与中间1交换后,稳定性已被打破。
8. G是一个非连通无向图(没有重边和自环),共有28条边,则该图至少有( )个顶点。
A.10 B.9 C.11 D.8
B。要求最小的点就是要尽可能占用边, n 个点的完全无向图最多占用n*(n+1)/2 条边,n=8的时候是8*7/2=28,意味着8个顶点最多有28条边。由于题目是求非连通图,则再加上单独第9个点。
9. 一些数字可以颠倒过来看,例如0、1、8颠倒过来看还是本身,6颠倒过来是9,9颠倒过来看还是6,其他数字颠倒过来都不构成数字。类似的,一些多位数也可以颠倒过来看,比如106颠倒过来是901。假设某个城市的车牌只有5位数字,每一位都可以取0到9。请问这个城市有多少个车牌倒过来恰好还是原来的车牌,并且车牌上的5位数能被3整除?( )
A.40 B.25 C.30 D.20
B。第1、2位有(0、1、8、6、9)五个数字,第3位有(0、1、8)三个数字,第4、5位由第1、2位决定。由于0,1,8模3正好余0,1,2,所以其他位确定则第3位自然确定,共5*5=25种。
10. 一次期末考试,某班有15人数学得满分,有12人语文得满分,并且有4人语、数都是满分,那么这个班至少有一门得满分的同学有多少人?( )
A.23 B.21 C.20 D.22
A。容斥原理,至少一门满分人数=数学满分+语文满分-语文数学满分=15+12-4=23。
11. 设A和B是两个长为n的有序数组,现在需要将A和B合并成一个排好序的数组,请问任何以元素比较作为基本运算的归并算法,在最坏情况下至少要做多少次比较?( )
A.n² B.n log n C.2n D.2n-1
D。两个数组从小到大依次比较,哪边小哪边入数组,当某一数组全部计入结果数组后,剩下的也依次进入。最好的情况是数组A所有数都比数组B第一个数小,只要比较n次。最坏情况是全部比较完,最后AB只剩最后一个数比较,总比较次数就是2n-1。
12. 以下哪个结构可以用来存储图?( )
A.栈 B.二叉树 C.队列 D.邻接矩阵
D。数据结构基础。
13. 以下哪些算法不属于贪心算法( )。
A.Di.jkstra算法 B.Floyd算法 C.Prim算法 D.Kruskal算法
B。Floyd算法枚举了全部情况自然不是贪心,其他算法均有取最小值。
14. 有一个等比数列,共有奇数项,其中第一项和最后一项分别是2和118098,中间一项是486,请问一下哪个数是可能的公比?( )。
A.5 B.3 C.4 D.2
B。直接代入看是否整除可以快速求得答案。可令公比为q,2*q^(2n-2)=118098,得q^(n-1)=248,四个选项中只有3是248的约数。
15. 有正实数构成的数字三角形排列形式如图所示。第一行的数为a(1,1),第二行a(2,1),a(2,2),第n行的数为a(n,1),a(n,2),…,a(n,n)。从a(1,1)开始,每一行的数a(i,j)只有两条边可以分别通向下一行的两个数a(i+1,j)和a(i+1,j+1)。用动态规划算法找出一条从a(1,1)向下通道a(n,1),a(n,2),…,a(n,n)中某个数的路径,使得该路径上的数之和最大。 令C[i][j]是从a(1,1)到a(i,j)的路径上的数的最大和,并且C[i][0]= C[0][j]=0,则C[i][j]=( )
A.max{C[i-1][j-1],C[i-1][j]}+ a(i,j) B.C[i-1][j-1]+C[i-1][j] C.max{C[i-1][j-1],c[i-1][j]}+1 D.max{C[i][j-1],C[i-1][j]}+ a(i,j)
A。每个点只能从上方两个点过来,自然取最大的加a(i,j)。
二、阅读程序
1.
1 #include <cstdio>
2 using namespace std;
3 int n;
4 int a[100];
5
6 int main() {
7 scanf("%d", &n);
8 for (int i = 1; i <= n; ++i)
9 scanf("%d", &a[i]);
10 int ans = 1;
11 for (int i = 1; i <= n; ++i) {
12 if (i > 1 && a[i] < a[i - 1])
13 ans = i;
14 while (ans < n && a[i] >= a[ans + 1])
15 ++ans;
16 printf("%d\n", ans);
17 }
18 return 0;
19 }
程序分析: 本段程序的目的是找到每个a[i]之后第一个大于a[i]的位置(下标ans+1)。代码简单,即使看不懂也可以代入几个数字去试验,毕竟选择题。
(1) 第16行输出ans时,ans的值一定大于i。( )
错。只要14行while循环不执行,则ans=i,如n=1,则ans等于i。
(2)程序输出的ans小于等于n。( )
对。ans初始值为i小于n,且小于n是其自增的一个条件,显然不会超过。
(3)若将第12行的“<”改为“!=”,程序输出的结果不会改变。( )
对。改成!=只是增加了一些没意义的比较,对结果没有影响。
(4)当程序执行到第16行时,若ans-i>2,则a[i+1]≦a[i]。( )
对。因为ans+1是第一个大于a[i]的位置,所以从a[i+1]至a[ans]都是小于等于a[i]的。
(5)若输入的a数组是一个严格单调递增的数列,此程序的时间复杂度是( )。
A.O(logn) B.O(n^2) C.O(nlog n) D. O(n)
D。严格单调递增则14行必定不执行,while循环每次只执行一次,时间复杂度为n。
(6)最坏情况下,此程序的时间复杂度是( )。
A. O(n^2) B. O(logn) C. O(n) D. O(nlog n)
A。最坏的情况为严格单调递减,14行if判断每次都执行,while循环每次都查找到n,时间复杂度为n+(n-1)+……+2+1=n*(n+1)/2,即O(n^2)。
2.
1 #include <iostream> 2 using namespace std; 3 4 const int maxn = 1000; 5 int n; 6 int fa[maxn], cnt[maxn]; 7 8 int getRoot(int v) { 9 if (fa[v] == v) return v; 10 return getRoot(fa[v]); 11 } 12 13 int main() { 14 cin >> n; 15 for (int i = 0; i < n; ++i) { 16 fa[i] = i; 17 cnt[i] = 1; 18 } 19 int ans = 0; 20 for (int i = 0; i < n - 1; ++i) { 21 int a, b, x, y; 22 cin >> a >> b; 23 x = getRoot(a); 24 y = getRoot(b); 25 ans += cnt[x] * cnt[y]; 26 fa[x] = y; 27 cnt[y] += cnt[x]; 28 } 29 cout << ans << endl; 30 return 0; 31 }
程序分析: 本题就是一个并查集操作,getRoot函数是查询根节点,循环中对集合进行合并。
(1)输入的a和b值应在[0,n-1]的范围内。( )
对。输入的a、b是集合的下标,自然应该在[0,n-1]之间。
(2) 第16行改成“fa[i]=0;”, 不影响程序运行结果。( )
错。未合并前初始根节点是其本身,为0显然不符合题意。
(3) 若输入的a和b值均在[0, n-1]的范围内,则对于任意0≤i<n,都有0≤fa[i]<n。( )
对。fa[i]是根节点,不管在怎么合并,根节点必然也是在[0,n-1]之间。
(4) 若输入的a和b值均在[0,n-1]的范围内,则对于任意0≤i<n,都有1≤cnt[i]≤n。( )
错。cnt[i]是合并之后集合的元素个数,严谨的并查集操作cnt[i]是不会超过n的,但是本题没有判断是否重复合并。所以如果先输入(1,2),(3,4),之后一直不断输入(2,4),最后cnt[4]会超过n。
(5) 当n等于50时,若a、b的值都在[0,49]的范围内,且在第25行时x总是不等于y,那么输出为( )。
A.1276 B.1176 C.1225 D.1250
C。本题前提下cnt[i]表示当前集合的元素个数,每次执行都是两个集合合并,我们可令每次都是单个集合合并进入大集合,ans=1*1+1*2+1*3+……1*48+1*49=49*(49+1)/2=1225。
(6) 此程序的时间复杂度是( )。
A. O(n) B. O(logn) C. O(n^2) D. O(nlogn)
C。getRoot是依次查找上一个父元素,没有“压缩路径”,时间复杂度最差为n,所以总的时间复杂度为n^2。
3.t是s的子序列的意思是:从s中删去若干个字符,可以得到t;特别的,如果s=t,那么t也是s的子序列;空串是任何串的子序列。例如:"acd"是“abcde”的子序列,“acd"是“acd”的子序列,但"adc” 不是“abcde”的子序列。
s[x..y]表示s[x] ...s[y]共y-x+l个字符构成的字符串,若x>y则 s[x..y]是空串。t[x..y]同理。
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 const int max1 = 202; 5 string s, t; 6 int pre[max1], suf[max1]; 7 8 int main() { 9 cin >> s >> t; 10 int slen = s.length(), tlen = t.length(); 11 12 for (int i = 0, j = 0; i < slen; ++i) { 13 if (j < tlen && s[i] == t[j]) ++j; 14 pre[i] = j; // t[0..j-1] 是 s[0..i] 的子序列 15 } 16 17 for (int i = slen - 1 , j = tlen - 1; i >= 0; --i) { 18 if(j >= 0 && s[i] == t [j]) --j; 19 suf[i]= j; // t[j+1..tlen-1] 是 s[i..slen-1] 的子序列 20 } 21 22 suf[slen] = tlen -1; 23 int ans = 0; 24 for (int i = 0, j = 0, tmp = 0; i <= slen; ++i){ 25 while(j <= slen && tmp >= suf[j] + 1) ++j; 26 ans = max(ans, j - i - 1); 27 tmp = pre[i]; 28 } 29 cout << ans << endl; 30 return 0; 31 }
程序分析: 本题主要需了解两个数组的含义,pre[i]表示s[0..i]至多可以从前往后匹配到t串的哪一个字符,此时t[0..pre[i]-1]是s[0..i]的子序列。sub[i]用于记录s[i..slen-1]至多从后到前匹配到t的哪一个字符,此时t[suf[i]+1..tlen-1]是s[i..slen-1]的子序列。本题是求s中连续删除至多几个字母后,t仍然是s的子序列。
(1) 程序输出时,suf数组满足:对任意0≤i<slen,suf[i] ≤suf[i+1]。( )
对。从15至19行可以看出,sub数组的值是从尾部往前减小或不变,所以suf[i]≤suf[i+1]。
(2) 当t是s的子序列时,输出一定不为0。( )
错。有题目目的可知,当t=s时输出为0。
(3) 程序运行到第23行时,“j-i-1”一定不小于0( )
错。若第一循环时while不执行,则j-i-1为-1,如s="bacb",t="ac"。
(4) 当t是s的子序列时,pre数组和suf数组满足:对任意0≤i<slen,pre[i]>suf[i+1]+1( )
错。由含义可知若t是s子序列,t[0..pre[i]-1],t[sub[i+1]+1..lent-1]是s[0..i],s[i+1..lens-1]的子序列,不会重叠,即pre[i]-1< sub[i+1]+1,即pre[i] <= sub[i+1]+1。
(5) 若tlen=10,输出为0,则slen最小为( )。 A. 10 B. 12 C.0 D.1
D。若t不是s子串(或t==s)输出都为0,但为保证程序执行,最少应输入一个字符。
(6) 若tlen=10,输出为2,则slen最小为( )。 A.0 B.10 C.12 D.1
C。输出为2说明slen最多连续删除2个后为10,所以最小为12。
三、完善程序(单选题,每小题3分,共计30分)
1.(匠人的自我修养)一个匠人决定要学习n个新技术。要想成功学习一个新技术,他不仅要拥有一定的经验值,而且还必须要先学会若干个相关的技术。学会一个新技术之后,他的经验值会增加一个对应的值。给定每个技术的学习条件和习得后获得的经验值,给定他已有的经验值,请问他最 多能学会多少个新技术。
输入第一行有两个数,分别为新技术个数n (l<=n<=10^3),以及己有经验值(<=10^7)。
接下来n行。第i行的两个正整数,分别表示学习第i个技术所需的最低经验值(<=10^7),以及学会第i个技术后可获得的经验值(<=10^7)
接下来n行。第i行的第一个数mi(0<=mi<n),表示第ii个技术的相关技术数量。紧跟着m个两两不同的数,表示第ii个技术的相关技术编号。
输出最多能学会的新技术个数。
下面的程序以(n^2)的时间复杂度完成这个问题,试补全程序。
#include<cstdio> using namespace std; const int maxn = 1001; int n; int cnt[maxn]; int child [maxn][maxn]; int unlock[maxn]; int threshold[maxn], bonus[maxn]; int points; bool find(){ int target = -1; for (int i = 1; i <= n; ++i) if(① && ②){ target = i; break; } if(target == -1) return false; unlock[target] = -1; ③ for (int i = 0; i < cnt[target]; ++i) ④ return true; } int main(){ scanf("%d%d", &n, &points); for (int i = 1; i <= n; ++i){ cnt[i] = 0; scanf("%d%d", &threshold[i], &bonus[i]); } for (int i = 1; i <= n; ++i){ int m; scanf("%d", &m); ⑤ for (int j = 0; j < m; ++j){ int fa; scanf("%d", &fa); child[fa][cnt[fa]] = i; ++cnt[fa]; } } int ans = 0; while(find()) ++ans; printf("%d\n", ans); return 0; }
程序分析: 程序每次都先学习一个已经达到条件但为学习的技能,学习后更新经验值和其他技能与该技能有关的学习条件,不断重复至没有技能可以学。unlock数组为对应技能需学习的前置技能数,大于0说明有前置技能要学,为-1表示已学习。
(1) ①处应填( )
A. unlock[i]<=0 B. unlock[i]>=0 C. unlock[i]==0 D. unlock[i]==-1
C。学习技能条件一,需要的前置技能数为0且为学习。
(2) ②处应填( )
A. threshold[i]>points B. threshold[i]>=points C. points>threshold[i] D. points>=threshold[i]
D。学习技能条件二,总经验达到该技能的经验要求。
(3) ③处应填( )
A. target = -1 B. --cnt[target] C. bbonus[target] D. points += bonus[target]
D。技能学习后总经验增加。
(4) ④处应填( )
A. cnt [child[target][i]] -=1 B. cnt [child[target][i]] =0 C. unlock[child[target][i]] -= 1 D. unlock[child[target][i]] =0
C。即使看不懂,也应该猜到是学完技能后,相关技能的前置条件-1。
(5) ⑤处应填( ) A. unlock[i] = cnt[i] B. unlock[i] =m C. unlock[i] = 0 D. unlock[i] =-1
B。定义每个技能学习需要的前置技能数。
2.(取石子)Alice和Bob两个人在玩取石子游戏。他们制定了n条取石子的规则,第ii条规则为:如果剩余石子的个数大于等于a[i]且大于等于b[i], 那么他们可以取走b[i]个石子。他们轮流取石子。如果轮到某个人取石子, 而他无法按照任何规则取走石子,那么他就输了。一开始石子有m个。请问先取石子的人是否有必胜的方法?
输入第一行有两个正整数,分别为规则个数n (l<n<64),以及石子个数 m (<=10^7)。
接下来nn行。第i行有两个正整数a[i]和b[i]。(l<=a[i]<=10^7,l<=b[i]<=64l)
如果先取石子的人必胜,那么输出“Win”,否则输出“Loss”。
提示:
可以使用动态规划解决这个问题。由于b[i]不超过64,所以可以使用64位无符号整数去压缩必要的状态。
status是胜负状态的二进制压缩,trans是状态转移的二进制压缩。
试补全程序。
代码说明:
~表示二进制补码运算符,它将每个二进制位的0变为1、1变为0;
而“^”表示二进制异或运算符,它将两个参与运算的数中的每个对应的二进制位一一进行比较,若两个二进制位相同,则运算结果的对应二进制位为0,反之为1。
ull标识符表示它前面的数字是unsigned long long类型。
1 #include <cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int maxn = 64; 5 int n, m; 6 int a[maxn], b[maxn]; 7 unsigned long long status, trans; 8 bool win; 9 int main(){ 10 scanf("%d%d", &n, &m); 11 for (int i = 0; i < n; ++i) 12 scanf("%d%d", &a[i], &b[i]); 13 for(int i = 0; i < n; ++i) 14 for(int j = i + 1; j < n; ++j) 15 if (aa[i] > a[j]){ 16 swap(a[i], a[j]); 17 swap(b[i], b[j]); 18 } 19 status = ①; 20 trans = 0; 21 for(int i = 1, j = 0; i <= m; ++i){ 22 while (j < n && ②){ 23 ③; 24 ++j; 25 } 26 win = ④; 27 ⑤; 28 } 29 30 puts(win ? "Win" : "Loss"); 31 32 return 0; 33 }
程序分析:通常我们可以设置bool数字f[i]表示有i个石子时有无先手必赢策略。若对于i个石子有先手必赢策略,则存在j(a[j]<=i且b[j]<=i)使得i-b[j]个石子先手无必赢策略,则得到转移方程f[i]=OR{!f[i-b[j]} (a[j]<=i且b[j]<=i)。因为本题策略数和数组b数字都不超过64,所以仅考虑f[i-1]..f[i-64],可将其状态压缩至一个64位数中。其中status用于记录对于i个石子,i-1..i-64是否有先手必胜策略。
(1)①处应填( )
A.0 B .~0ull C.~0ull^1 D.1
C。根据题目要求,状态压缩到64位,A和D默认是32位整数,所以B或者C。最开始石子是0个,应该是输的状态,所以最低位不能是1,选C。
(2)处应填( )
A.a[j]< i B.a[j]==i C.a[j] !=i D.a[j] >i
B。题目实现有将规则按a[i]进行从小到大排序,所以可使用规则只增加不减少。此循环用于增加当前可选的规则,当i达到a[j]时规则可以使用。
(3)③处应填( )
A. trans |= 1ull <<(b[j] - 1) B. status |= 1ull << (b[j]- 1) C. status += 1ull << (b[j]-1) D. trans += 1ull<< (b[j]-1)
A。此行是用来在原有规则上,新增”取b[j]个棋子”的规则。二进制新增用|。
(4)④处应填( )
A. ~status|trans B. status&trans C. status|trans D. ~status&trans
D。
(5)⑤处应填( )
A. trans = status | trans ^win B. status = trans >> 1^win C. trans = status ^trans |win D. status =status <<1^win
D。
https://www.bilibili.com/read/cv3833576/
出处: bilibili