二、阅读程序
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进制数的最大值为
所以n最大值为
\[k^{len}-1 \]4.D
k=1是特殊的一种情况,这种情况下,跟踪代码可以发现len将始终为2,后面每次d[0]自增1,都会同时使d[1]自增1,ans的值就等于n
5.B
这里是在3进制下,将一个数从0自增到
过程中产生的低位到高位进位的次数,最低位到最高位依次记为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],有
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
小结
本次阅读程序题,难度基本是依次递增,第一题相对最容易,第二,三题相对更难,并且涉及较多的计算,特别是第三题,考察了考生对回溯算法套路的掌握程度,只有在非常熟悉回溯算法套路的情况下才能快速理清思路,求解该题,否则在考场有限的考试时间内,很难得到正确结果,所以我们也可以看懂普及组初赛难度也是在逐年增加,大家决不能掉以轻心。