PAT乙級冬仿真卷(C語言)解析
十天前晚上看到PAT乙級考試的時間又延期了,就萌生了線上測試的想法。充值了十元大錢,進行了線上測試。
測試結果說來慚愧,20分鐘AC了前三道題,第四道題格式錯誤,乍一看沒有發現格式上的問題,就開始做第五道題了。第五道題在最後兩個測試點上卡到考試結束,在第五道題debug過程中順便解決了第四道題的格式錯誤。由於測試點5是超時,需要改進算法,測試點6是答案錯誤,存在bug。所以我一直就槓在測試點6上,始終沒想明白測試點6到底是在什麼地方報了錯。離考試只剩半小時時放棄了思考,懷疑這題目是不是有問題。於是粘貼了網上的C++代碼,結果別人的代碼通過了。我也懂C++的語法,但是並沒有看出測試點6爲什麼沒通過。於是當晚就想棄坑PAT。連續十天沒有再上PAT上做題了。直到昨天重新開始OJ,做到PAT乙級1025,才發現這道題是從這改編而來,基本沒變。而1025的測試點6,在網上也有很多文章提到“遊離結點”這個坑點。這個測試點6我至今都是不服氣的。
下面逐題解析。
7-1 2019數列 (15分)
把 2019 各個數位上的數字 2、0、1、9 作爲一個數列的前 4 項,用它們去構造一個無窮數列,其中第 n(>4)項是它前 4 項之和的個位數字。例如第 5 項爲 2, 因爲 2+0+1+9=12,個位數是 2。
本題就請你編寫程序,列出這個序列的前 n 項。
輸入格式:
輸入給出正整數 n(≤1000)。
輸出格式:
在一行中輸出數列的前 n 項,數字間不要有空格。
輸入樣例:
10
輸出樣例:
2019224758
題外話:這個數列中永遠不會出現 2018,你能證明嗎?
AC代碼
- 題目中的數列構造方式與“斐波那契數列”的構造方式相似。斐波那契數列的第n(>2)爲前兩項數字之和。
#include<stdio.h>
int main(){
int N,num[1000]={2,0,1,9}; //初始化前四項
for(int i=4;i<1000;i++){ //逐項推導
num[i]=(num[i-1]+num[i-2]+num[i-3]+num[i-4])%10;
}
scanf("%d",&N); //由於有輸出前四項的可能,故而乾脆將前1000項都推導出,按輸入來輸出
for(int i=0;i<N;i++){
printf("%d",num[i]);
}
return 0;
}
7-2 老鼠愛大米 (20分)
翁愷老師曾經設計過一款 Java 挑戰遊戲,叫“老鼠愛大米”(或許因爲他的外號叫“胖胖鼠”)。每個玩家用 Java 代碼控制一隻鼠,目標是搶吃儘可能多的大米讓自己變成胖胖鼠,最胖的那隻就是冠軍。
因爲遊戲時間不能太長,我們把玩家分成 N 組,每組 M 只老鼠同場競技,然後從 N 個分組冠軍中直接選出最胖的冠軍胖胖鼠。現在就請你寫個程序來得到冠軍的體重。
輸入格式:
輸入在第一行中給出 2 個正整數:N(≤100)爲組數,M(≤10)爲每組玩家個數。隨後 N 行,每行給出一組玩家控制的 M 只老鼠最後的體重,均爲不超過的非負整數。數字間以空格分隔。
輸出格式:
首先在第一行順次輸出各組冠軍的體重,數字間以 1 個空格分隔,行首尾不得有多餘空格。隨後在第二行輸出冠軍胖胖鼠的體重。
輸入樣例:
3 5
62 53 88 72 81
12 31 9 0 2
91 42 39 6 48
輸出樣例:
88 31 91
91
AC代碼
#include<stdio.h>
#include<stdlib.h>
int cmp(void *_a,void *_b){ //qsort的回調函數
int a=*(int *)_a;
int b=*(int *)_b;
return b-a;
}
int main(){
int N,M;
scanf("%d %d",&N,&M);
int temp[N][M], //原始輸入數據
cham[N]; //每組冠軍
for(int i=0;i<N;i++){
cham[i]=-1;
for(int j=0;j<M;j++){
scanf("%d",&temp[i][j]);
if(temp[i][j]>cham[i])cham[i]=temp[i][j]; //找組冠軍
}
}
int No1=-1; //總冠軍
for(int i=0;i<N;i++){
if(i!=0)printf(" ");
printf("%d",cham[i]);
if(cham[i]>No1)No1=cham[i]; //找總冠軍
}
printf("\n%d",No1);
return 0;
}
7-3 String復讀機 (20分)
給定一個長度不超過的、僅由英文字母構成的字符串。請將字符重新調整順序,按StringString
… (注意區分大小寫)這樣的順序輸出,並忽略其它字符。當然,六種字符的個數不一定是一樣多的,若某種字符已經輸出完,則餘下的字符仍按 String 的順序打印,直到所有字符都被輸出。例如 gnirtSSs
要調整成 StringS
輸出,其中 s
是多餘字符被忽略。
輸入格式:
輸入在一行中給出一個長度不超過的、僅由英文字母構成的非空字符串。
輸出格式:
在一行中按題目要求輸出排序後的字符串。題目保證輸出非空。
輸入樣例:
sTRidlinSayBingStrropriiSHSiRiagIgtSSr
輸出樣例:
StringStringSrigSriSiSii
AC代碼
#include<stdio.h>
int main(){
char temp; //接收輸入字符
char chr[6]={'S','t','r','i','n','g'}; //輸出字符表
int cnt[6]={0}; //對應的字符數量
while((temp=getchar())!='\n'){
switch(temp){ //統計各字符數量
case 'S':cnt[0]++;break;
case 't':cnt[1]++;break;
case 'r':cnt[2]++;break;
case 'i':cnt[3]++;break;
case 'n':cnt[4]++;break;
case 'g':cnt[5]++;break;
default :break;
}
}
for(int flag;flag;){ //循環遍歷輸出字符表,相應字符數量有剩餘就輸出,全部輸出完退出循環
flag=0;
for(int i=0;i<6;i++){
if(cnt[i]!=0){
printf("%c",chr[i]);
cnt[i]--;
flag=1;
}
}
}
return 0;
}
7-4 擅長C (20分)
題目太長,省略。
輸入格式:
輸入首先給出 26 個英文大寫字母 A-Z,每個字母用一個 7×5 的、由 C 和 . 組成的矩陣構成。最後在一行中給出一個句子,以回車結束。句子是由若干個單詞(每個包含不超過 10 個連續的大寫英文字母)組成的,單詞間以任何非大寫英文字母分隔。
題目保證至少給出一個單詞。
輸出格式:
對每個單詞,將其每個字母用矩陣形式在一行中輸出,字母間有一列空格分隔。單詞的首尾不得有多餘空格。
相鄰的兩個單詞間必須有一空行分隔。輸出的首尾不得有多餘空行。
AC代碼
- 題中:單詞間以任何非大寫英文字母分隔。之間可能有多個字符進行單詞的分隔。我第一次提交時就被這個卡住了,以爲是單個字符分隔,只做了單個字符的排除。後來多次審題才發現可能有這麼一個坑。
#include<stdio.h>
int main(){
char list[26][7][5]; //三維數組存放字母矩陣
for(int i=0;i<26;i++){
for(int j=0;j<7;j++){
for(int k=0;k<5;k++){
scanf(" %c",&list[i][j][k]);
}
}
}
getchar(); //吸收字母矩陣後的回車
char word[12],temp;
int flag=0;
while((temp=getchar())!='\n'){
int count=0; //單詞長度
for(count=0;'A'<= temp && temp <='Z';count++){
word[count]=temp;
temp=getchar();
}
if(count==0)continue; //非大寫英文字母,跳過
//printf("%s\ncount=%d\n",word,count);
if(flag)printf("\n\n");
for(int j=0;j<7;j++){ //輸出單詞
if(j!=0)printf("\n");
for(int t=0;t<count;t++){
int some=word[t]-'A';
if(t!=0)printf(" ");
for(int k=0;k<5;k++){
printf("%c",list[some][j][k]);
}
}
flag=1;
}
if(temp =='\n'){ //讀入單詞時因回車而結束退出循環。
break;
}
}
return 0;
}
7-5 區塊反轉 (25分)
給定一個單鏈表 L,我們將每 K 個結點看成一個區塊(鏈表最後若不足 K 個結點,也看成一個區塊),請編寫程序將 L 中所有區塊的鏈接反轉。例如:給定 L 爲 1→2→3→4→5→6→7→8,K 爲 3,則輸出應該爲 7→8→4→5→6→1→2→3。
輸入格式:
每個輸入包含 1 個測試用例。每個測試用例第 1 行給出第 1 個結點的地址、結點總個數正整數 N (≤)、以及正整數 K (≤N),即區塊的大小。結點的地址是 5 位非負整數,NULL 地址用 −1 表示。
接下來有 N 行,每行格式爲:
Address Data Next
其中 Address
是結點地址,Data
是該結點保存的整數數據,Next
是下一結點的地址。
輸出格式:
對每個測試用例,順序輸出反轉後的鏈表,其上每個結點佔一行,格式與輸入相同。
輸入樣例:
00100 8 3
71120 7 88666
00000 4 99999
00100 1 12309
68237 6 71120
33218 3 00000
99999 5 68237
88666 8 -1
12309 2 33218
輸出樣例:
71120 7 88666
88666 8 00000
00000 4 99999
99999 5 68237
68237 6 00100
00100 1 12309
12309 2 33218
33218 3 -1
AC代碼
- 這題是PAT1025的變體。
- 網上看這題的代碼不多,但1025的C++代碼可以說是“八仙過海,各顯神通”了。
- 再次吐槽測試點6:題目說好了給一個單鏈表,爲啥還有遊離結點呢???如果一定要這樣測試,題目應該說給一堆結點,而不是說給一個單鏈表。真的氣,之前死磕這個點找不到問題所在,結果居然是這個問題。我真的,瞬間就想棄坑PAT了。
- 這題是組間反轉,1025的是組內反轉。我是將這個問題轉成排序問題做的,所以本題代碼和1025的幾乎是一樣的。
#include<stdio.h>
#include<stdlib.h>
typedef struct{ //定義結點
int addr;
int data;
int next;
int grade1; //分組編號
int grade2; //組內結點編號
}list;
int cmp(void *_a,void *_b){ //排序規則
list *a=(list *)_a;
list *b=(list *)_b;
if(b->grade1 != a->grade1){ //非同組按分組編號降序排列
return b->grade1-a->grade1;
}
else{ //同組按組內結點編號升序排列
return a->grade2-b->grade2;
}
}
int main(){
int N,head,K;
scanf("%d %d %d",&head,&N,&K);
list a[100000],b[N]; //原始輸入數據,按鏈表順序存入數組後的數據
for(int i=0;i<N;i++){ //輸入數據
int addr;
scanf(" %d",&addr); //獲得地址
scanf(" %d %d",&a[addr].data,&a[addr].next); //存入該地址對應下標的數組元素中
}
int cnt=0; //統計鏈表中的結點數,即排除遊離結點
b[cnt]=a[head]; //設置頭結點
b[cnt].addr=head;
while(b[cnt].next!=-1){ //按鏈表順序存入數組b
int addr=b[cnt].next; //暫存下個結點地址
cnt++;
b[cnt]=a[addr]; //對應結點存入b數組中
b[cnt].addr=addr; //記錄地址
b[cnt].grade1=cnt/K; //運算得到組編號,被K除後得數相同的爲同一組
b[cnt].grade2=cnt%K; //運算得到組內結點編號,被K除後的餘數表示它是組內第幾個元素
}
qsort(b,(cnt+1),sizeof(list),cmp); //按規則排序
// for(int i=0;i<cnt;i++){ //修改後繼節點信息,這裏省時未修改
// b[i].next=b[i+1].addr;
// }
for(int i=0;i<cnt;i++){ //輸出
printf("%05d %d %05d\n",b[i].addr,b[i].data,b[i+1].addr);
//未修改後繼結點信息,後繼結點是錯值,最後一項輸出上個結點的後繼結點地址
}
printf("%05d %d -1",b[cnt].addr,b[cnt].data);
return 0;
}
- 疫情的緣故,開學大概率是無限期推遲了。原先報名的PAT乙級考試也在當時棄坑情緒下退考了。至於以後會不會再去刷PAT甲級,到時候再說吧。
- 學了C++之後,越發覺得C語言在OJ上的吃力。乙級的難度不大,之後的乙級練習還是繼續提交C代碼,以加深對C語言的理解,及減少對各種庫函數的依賴。