ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

CSP2020-J1-真题解析-阅读程序

2021-06-14 22:36:13  阅读:321  来源: 互联网

标签:15 int CSP2020 30 J1 len 阅读程序 ans 进位


二、阅读程序

1. 编解码

#include <cstdlib>
#include <iostream>
using namespace std;

char encoder[26] = {'C','S','P',0};
char decoder[26];

string st;

int main()  {
  int k = 0;
  for (int i = 0; i < 26; ++i)
    if (encoder[i] != 0) ++k;
  for (char x ='A'; x <= 'Z'; ++x) {
    bool flag = true;
    for (int i = 0; i < 26; ++i)
      if (encoder[i] ==x) {
        flag = false;
        break;
      }
      if (flag) {
        encoder[k]= x;
        ++k;
      }
  }
  for (int i = 0; i < 26; ++i)
     decoder[encoder[i]- 'A'] = i + 'A';
  cin >> st;
  for (int i = 0; i < st.length( ); ++i)
    st[i] = decoder[st[i] -'A'];
  cout << st;
  return 0;
}

【分析】此题最直接的方法就是,首先根据代码逻辑将decoder和encoder两个数组用表格画出来,不要节省草稿纸,完整的画出来,你会看得更清楚,且不容易出错。
在这里插入图片描述
encoder数组就是A-Z字母序列,将C,S,P这3个字母移到了最前面,然后形成的字母列表,所以原先排在C,S,P这3个字母前面的字母统统后移,S后面的字母从T-Z这7个字母位置不变。
decoder数组,将encoder在下标i上的字母的在原字母表的位置上放置原字母表的第i个字母。
对于输入字符串st中的字母,根据表中的第一行查找对应decoder表对应的字母就是输出的字母。

1.正确
显然输入的字符串st只能有大写字母组成,因为encoder和decoder数组长度都只有26,第30行代码

st[i] = decoder[st[i] -'A'];

如果st[i]不是大写字母,则st[i] -'A'的值就会超过0-25的范围,导致此处数组越界
2.错误
显然若输入字母是T-Z字母组成,则输入和输出相同
3.正确
因为第12行统计encoder数组中的非零字符,只有3个,只要不小于3就可以
4.错误
第26行是要遍历整个encoder数组,生成decoder数组,必须要完整遍历26次
5和6直接根据表格即可得出,表格第一行是输入字母,最后一行是对应的输出字母。

2. 进位统计

#include <iostream>
using namespace std;

long long n, ans;
int k, len;
long long d[1000000];

int main() {
  cin >> n >> k;
  d[0] = 0;
  len= 1;
  ans = 0;
  for (long long i = 0; i <n; ++i) {
    ++d[0];
    for (int j = 0; j + 1<len; ++j) {
      if (d[j] == k) {
        d[j] = 0;
        d[j + 1] += 1;
        ++ans;
      }
    }
    if (d[len- 1] == k) {
      d[len - 1] = 0;
      d[len] =1;
      ++len;
      ++ans;
    }
  }
  cout << ans << endl;
  return 0;
}

【分析】仔细观察13行代码开始的for循环,循环n次,每次循环都给d[0]加1,然后继续看后面的代码,首先是15-21行的内层的for循环,遍历下标j从0~len-2,依次检测d[j]看其是否达到k,若达到k,则将d[j]置0,然后d[j+1]加1,同时ans加1。然后是22-27行,是检测d[len-1]是否达到k,若达到k,则将d[len-1]置0,然后d[len]置1,同时len加1,ans加1。
可以推测出,d数组最终的结果是十进制数n的k进制的表示,从左至右,是低位到高位,从0开始,d数组初值均为0,每次在最低位d[0]加1,循环过程整记录低位向高位的进位次数,len表示当前转换的k进制数的位数。
所以15-21行是处理低位的进位,22-27行是处理最高位的进位,最高位若进位,则总的数位数len加1。

1.错误
举反例,当k=1时,若n=1,则输出ans时,len=2
2.错误
举反例,当k>1时,若n=1,则输出ans时,len=1
3.正确
k>1时,len是n转为k进制数的总位数,显然len位k进制数的最大值为

\[k^{len}-1 \]

所以n最大值为

\[k^{len}-1 \]

4.D
k=1是特殊的一种情况,这种情况下,跟踪代码可以发现len将始终为2,后面每次d[0]自增1,都会同时使d[1]自增1,ans的值就等于n
5.B
这里是在3进制下,将一个数从0自增到

\[3^{30} \]

过程中产生的低位到高位进位的次数,最低位到最高位依次记为d[0],d[1],d[2],...d[30],最终结果依次是0, 0, 0, ..., 1,d[0]每自增3次就向d[1]进位,d[1]每自增3次就向d[2]进位,d[1]每自增3次等价于d[0]自增9次,依次类推,最后一次进位是d[29]向d[30]进位,是d[0]自增$$3^{30}$$次次产生的进位,所以总的进位数需要计算全部的低位向高位的进位:
d[0]->d[1],有

\[\frac{3^{30}}{3}=3^{29}次 \]

d[1]->d[2],有

\[\frac{3^{30}}{3^2}=3^{28}次 \]

......

d[29]->d[30],有

\[\frac{3^{30}}{3^{30}}=1次 \]

总进位次数=

\[1+3+3^2+...+3^{29} = \frac{3^{30}-1}{2} \]

6.D
在前面第5题的分析基础上,本题也迎刃而解,10进制下,不用转换,n本身就是10进制展示的,将100,010,002,000,090依次拆分为下表格左边所示的4个数,参照第5题,求出每个数的进位总数,累加即可得到结果。
在这里插入图片描述

3. 回溯递归

#include <algorithm>
#include <iostream>
using namespace std;                     

int n;                                   
int d[50][2];                            
int ans;                                 

void dfs(int n, int sum) {               
  if (n == 1) {                            
    ans = max(sum, ans);           
    return;                                   
  }                                        
  for (int i = 1; i < n; ++i) {            
    int a = d[i - 1][0], b = d[i - 1][1];  
    int x = d[i][0], y = d[i][1];            
    d[i - 1][0] = a + x;                     
    d[i - 1][1] = b + y;                     
    for (int j = i; j < n - 1; ++j)            
      d[j][0] = d[j + 1][0], d[j][1] = d[j + 1][1];
    int s = a + x + abs(b - y);              
    dfs(n - 1, sum + s);                    
    for (int j = n - 1; j > i; --j)          
      d[j][0] = d[j - 1][0], d[j][1] = d[j - 1][1];
    d[i - 1][0] = a, d[i - 1][1] = b;        
    d[i][0] = x, d[i][1] = y;                
  }                                        
}                                        

int main() {                             
  cin >> n;                                
  for (int i = 0; i < n; ++i)              
  cin >> d[i][0];
  for (int i = 0; i < n;++i)
     cin >> d[i][1];
  ans = 0;
  dfs(n, 0);
  cout << ans << endl;
  return 0;
}

【分析】此题相比前两题难度明显增加。首先拿到此题,一下子不容易读懂这题究竟是求的什么,但是有一个很明显的特征就是dfs这个函数,是很典型的DFS回溯算法,从回溯算法套路入手,回溯算法的套路模板大致如下:

void traceback(n, ...){
    if(达到叶子节点){
        更新结果,返回
    }
    //遍历n-1个子节点
    for(i=1, i<n, i++){
        依次做选择,选择某个子节点
        设置现场,可能有一些值将被临时修改
        //递归调用
        traceback(n-1, ...)
        撤销本次选择,恢复现场,还原被修改的值
    }
}

搜索树结构大致入下图所示,比如n=4的时候。
在这里插入图片描述

dfs函数的第一个参数n,可以认为是搜索树的高度,根节点高度为n,叶子节点高度为1,dfs函数的第二个参数是当前搜索节点下的累积结果,每一条搜索路径搜索到n=1时到达叶子节点,得到一个当前最大的ans值,返回,然后逐层往上回溯,继续搜索其他路径,寻找更大的ans值,并更新ans值。
对照代码,15-21行是设置现场,也就是当前选择节点i之后,进行的一系列的计算,得到s值,然后追加到sum,进行下一层递归调用,递归返回后,23-26是撤销选择,恢复现场。
然后具体分析具体的代码逻辑,主要是15-21行,如下图所示:
在这里插入图片描述

每次递归执行到当前子节点的第i个节点时,将d数组的第i行累加到第i-1行,然后从数组的尾部开始逐个覆盖前置元素,直到第i行,然后计算sum的值,d数组的第i-1,i行第1列的两个元素的和加上第2列两个元素的差的绝对值,sum累加到ans,传递给下一层递归调用。这样递归调用直到叶子节点,求得某条路径下的一个ans,并更新当前ans取较大值,然后回溯再搜索。全部路径搜索完之后,得到最终的最大的ans值。

1.错误
n=0时,dfs函数不会执行任何代码,直接返回,ans=0,程序不会报错
2.正确
若d数组的元素全为0,则所有的sum计算结果都是0,ans不会被更新,也是0
3.错误
很显然,根据第2题的结论,当输入均为0时,输出结果为0,并不小于任何输入的数
4.B
当d[i][0]是20个9,d[i][1]是20个0时,则每次计算s = a + x + abs(b - y)时,abs(b-y)则恒为0,可以直接忽略,所以每次累加的是d[i-1][0]和d[i][0],很显然,dfs搜索路径的最左边的路径搜索下来得到的累加结果ans应该是最大的,因为沿着这条路径搜索,每次都是d[1][0]累加到d[0][0],从第2层节点开始,s=9+9=9×2,第3层,s=9×3,直到第20层节点,s=9×20,d[0][0]总是保持顶端优势,为最大。所以ans=9×2+9×3+...+9×20=1881
5.C
当d[i][0]是30个0,d[i][1]是30个5时,则每次计算s = a + x + abs(b - y)时,a+x则恒为0,可以直接忽略,所以每次累加的是d[i-1][1]和d[i][1]的差的绝对值,从第2层开始直到第30层,s依次为:
5-5=0
2×5-5=5
3×5-5=2×5
......
29×5-5=28×5
所以ans=5+2×5+3×5+...+28×5=2030
6.C
结合第4,5两题,这里需要分别计算a+x和abs(b-y)两部分,然后累加,首先a+x,从第2层直到第15层叶子层:
15+14
15+14+13
...
15+14+13+...+2+1
然后abs(b-y)
15-14
15+14-13
15+14+13-12
...
15+14+13+...+2-1
这两块加起来求和,注意一定要算仔细,不要出错,结果是C, 2240

小结

本次阅读程序题,难度基本是依次递增,第一题相对最容易,第二,三题相对更难,并且涉及较多的计算,特别是第三题,考察了考生对回溯算法套路的掌握程度,只有在非常熟悉回溯算法套路的情况下才能快速理清思路,求解该题,否则在考场有限的考试时间内,很难得到正确结果,所以我们也可以看懂普及组初赛难度也是在逐年增加,大家决不能掉以轻心。

标签:15,int,CSP2020,30,J1,len,阅读程序,ans,进位
来源: https://www.cnblogs.com/HustJackyan/p/14883584.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有