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
0
4
10^4
104的非负整数。数字间以空格分隔。
输出格式:
首先在第一行顺次输出各组冠军的体重,数字间以 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分)
给定一个长度不超过
1
0
4
10^4
104的、仅由英文字母构成的字符串。请将字符重新调整顺序,按StringString
… (注意区分大小写)这样的顺序输出,并忽略其它字符。当然,六种字符的个数不一定是一样多的,若某种字符已经输出完,则余下的字符仍按 String 的顺序打印,直到所有字符都被输出。例如 gnirtSSs
要调整成 StringS
输出,其中 s
是多余字符被忽略。
输入格式:
输入在一行中给出一个长度不超过
1
0
4
10^4
104的、仅由英文字母构成的非空字符串。
输出格式:
在一行中按题目要求输出排序后的字符串。题目保证输出非空。
输入样例:
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 (≤
1
0
5
10^5
105)、以及正整数 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语言的理解,及减少对各种库函数的依赖。