二、閱讀程序
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最大值為
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],有
......
d[29]->d[30],有
總進位次數=
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
小結
本次閱讀程序題,難度基本是依次遞增,第一題相對最容易,第二,三題相對更難,並且涉及較多的計算,特別是第三題,考察了考生對回溯算法套路的掌握程度,只有在非常熟悉回溯算法套路的情況下才能快速理清思路,求解該題,否則在考場有限的考試時間內,很難得到正確結果,所以我們也可以看懂普及組初賽難度也是在逐年增加,大家決不能掉以輕心。