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行的第一个数mi0<=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
posted @ 2020-10-07 18:13  青鸢挽歌  阅读(3266)  评论(3编辑  收藏  举报
浏览器标题切换
浏览器标题切换end