【PAT乙级】PAT (Basic Level) Practice (中文)训练记录

为了备考PAT考试,近期在刷PAT的习题,从易到难。


什么是PAT:
浙江大学计算机程序设计能力考试(Programming AbilityTest,简称PAT)是由浙江大学计算机科学与技术学院组织的统一考试。旨在培养和展现学生分析问题、解决问题和计算机程序设计的能力,科学评价计算机程序设计人才,并为企业选拔人才提供参考标准。

PAT乙级要求掌握的知识:
1.具备基本的C/C++的代码设计能力,掌握相关开发环境的基本调试技巧;
2.理解并掌握最基本的数据结构,如:线性表、树、图等;
3.理解并熟练编程实现与基本数据结构相关的基础算法,包括递归、排序、查找等;
4.学会分析算法的时间复杂度、空间复杂度和算法稳定性;
5.具备问题抽象和建模的初步能力,并能够用所学方法解决实际问题。

本博客用于记录在做PAT乙级题库的时候的新得。同时对一些题目的思路,不容易一次通过的测试要点也会进行记录

本文已经全部更新完毕

PAT乙级也考完了,附上一个成绩单,还需要继续学习:
在这里插入图片描述


博客中涉及的代码均存储在我的GitHub仓库中,如有需要可以直接点击链接查看或下载:

PAT乙级代码所有代码均AC通过
在这里插入图片描述


目录


PAT乙级题库

PAT乙级题库

1001 害死人不偿命的(3n+1)猜想

题目:害死人不偿命的(3n+1)猜想

思路:这个题很简单,通过对n简单的循环与判断即可实现

代码:害死人不偿命的(3n+1)猜想

1002 写出这个数

题目:写出这个数

思路:需要将正整数存在字符串中,然后将每个字符转换为数字进行加和,最后将求和的数字中各个位数字取出来,按照顺序输出对应拼音即可

代码:写出这个数

1003 我要通过!

题目:我要通过!

思路:需要定义一个函数判断什么样的字符串可以通过,关键在于对第三个条件,如何转化为一个表达式是关键

int judge(string s)//在这里我定义了一个返回值为int类型的函数用于判断字符串s是否满足要求
{
   
   //定义int类型返回值,而不是bool类型,目的是调试的时候根据不同的返回值判断是哪种情况
//规定返回值0是答案正确,大于0是答案错误
	int len = s.length();//读取字符串的长度
	int p = s.find("P");//找到字符串中第一个出现的P字母
	int t = s.find("T");//找到字符串第一个出现的T字母
	if (t-p<2)return 1;//若P在T后,或者PT间隔中没有字母,返回1,例如APT,或者ATP这种都会返回1
	for (int i = p + 1; i < t; i++)//查找PT中间的字符
	{
   
   
		if (s[i] != 'A')return 2;//中间字符如果有不是A的,返回2,例如PAACT这种
	}
	//根据题目要求:如果 aPbTc 是正确的,那么 aPbATca 也是正确的,翻译一下就是PT中每增加一个A,字符最后需要增加P前相同的字符,因此P前的字符数*PT中间的字符数=T后面的字符数
	//例如AAPAATAAAA,P前2个,PT中间2个,T后面4个
	if (p*(t-p-1) != len - t - 1)return 3;//这是对上一步的判断,不满足这个结果的话就返回3
	for (int i = 0; i < p; i++)
	{
   
   
		if (s[i] != 'A')return 4;//判断P前的字符是不是都是A,如果不是返回4
	}
	for (int i = t+1; i < len; i++)//判断P后面的字符是不是都是A,如果不是返回5
	{
   
   
		if (s[i] != 'A')return 5;
	}
	return 0;//只有满足上述所有条件才能返回0,得到答案正确
}

代码:我要通过!

1004 成绩排名

题目:成绩排名

思路:本题较简单,定义结构数组,使用冒泡排序即可(或者使用算法里的sort函数)

代码:成绩排名

1005 继续(3n+1)猜想

题目:继续(3n+1)猜想

思路:将给定数组中的每一个数的计算数列记录下来,然后对这一系列数组进行消除处理,即:将每个数列的第一个数,在其他数列中(从第二位开始)查找,如果在其他数列中找到了,则证明其他数列包含这个数列,清空自己的数组数列,循环执行,最后留下的就是互相不包含的数组。

//关键的查找循环体,其中cal是一个二维的vecotor,存放每一个数的计算数列
vector<int>::iterator it;
for (int i = 0; i < K; i++)//对每一个数列与其他数列比较,如果其第一个元素存在其他数列中,清空该数列,并追加首位为0与次位的0
	{
   
   
		for (int j = 0; j < K; j++)
		{
   
   
			it = find(cal[j].begin() + 1, cal[j].end(), cal[i][0]);//从i+1行的第二个元素开始找
			if (it != cal[j].end())//如果在j中找到,清除自己,并追加0以便之后别的数组查找
			{
   
   
				cal[i].clear();
				cal[i].push_back(0);
				cal[i].push_back(0);
				break;
			}
		}
	}

查找完后余下一堆[0,0]的数组以及相互包含的数组,取出数组的首位降序输出即可

代码:继续(3n+1)猜想

1006 换个格式输出整数

题目:换个格式输出整数

思路:本题较简单,读取出百位,十位,个位后按照要求输出即可。

用 12…n 来表示不为零的个位数字 n(<10)

注意这句话的意思是从1开始循环输出,例如n是2,输出12,n是3,输出123,n是5输出12345

代码:换个格式输出整数

1007 素数对猜想

题目:素数对猜想

思路:本题难度不大,关键在于判断素数的程序运算效率要高,不然会出现运行超时

bool judge(int n)
{
   
   
	if (n == 2) return true;//2是素数
	if (n % 2 == 0)return false;//其他偶数都不是素数
	for (int i = 3; i <= sqrt(n); i+=2)//从3开始除,步长为2,判断速度加快一倍
	{
   
   
		if (n%i == 0)return false;
	}
	return true;
}

代码:素数对猜想

1008 数组元素循环右移问题

题目:数组元素循环右移问题

思路:本题难度不大,要求不借助其他数组,可以使用deque队列,两端进行删除添加效率更高

代码:数组元素循环右移问题

1009 说反话

题目:说反话

思路:使用队列往前段循环输入字符串,然后顺序输出即可

代码:说反话

1010 一元多项式求导

题目:一元多项式求导

思路:定义两个数组,一个用于存放系数,一个存放指数,然后对系数乘以指数即为求导后的系数(分常数项与非常数项)

代码:一元多项式求导

1011 A+B 和 C

题目:A+B 和 C

思路:此题问题很简单,但是要注意的是本题要求计算的整数范围为[-231,231],因此A+B的范围为[-232,232],已经超出C++中int数据类型的范围,因此如果使用int类型会有一些测试用例无法通过,应该使用长整型变量

C++中long与int的范围一样,使用long long类型

代码:A+B 和 C

1012 数字分类

题目:数字分类

思路:此题较简单,使用vector组对数字进行分类储存即可

代码:数字分类

1013 数素数

题目:数素数

思路:此题较简单,通过动态数组存储数据,注意判断素数的程序尽量高效,详情参见1007题。

代码:数素数

1014 福尔摩斯的约会

题目:福尔摩斯的约会

思路:此题的主要陷阱在于判断的字符必须是范围内的数组数据,不属于范围内的字符相同是无效的。

//范围内的数组数据
const string code[7] = {
   
   "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" };
char t[] = {
   
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N' };

前两个字符串第一个相同的字符必须是A-G(代表1-7),第二个相同的字符必须是0-9或者A-N(代表0-23)后两个字符串第一个相同的字符必须是字母,找到相同元素中的字符位置就解决问题了

//使用count来记录判断到第几个数组,count=0表示还没找到第一个相同的A-G间的字符
//count=1表示已经找到了存放星期的字符,要找存放小时的字符了
for (int i = 0; i < a.size()||i<b.size(); i++)
	{
   
   
		if (count == 0 && a[i] == b[i] && (a[i] >= 'A'&&a[i] <= 'G'))//存放星期
		{
   
   
			week = a[i];
			count++;
			continue;
		}
		if (count == 1 && a[i] == b[i] && ((a[i] >= 'A'&&a[i] <= 'N') || (a[i] >= '0'&&a[i] <= '9')))//存放小时
		//必须保证相同的字符是t数组中的
		{
   
   
			hour = a[i];
			break;
		}
	}

代码:福尔摩斯的约会

1015 德才论

题目:德才论

思路:此题有三个关键点

  • 根据题干对学生正确的分类

根据题目要求划分人物:(注意是用if-else if的判断,前面判断不通过才进入后面的判断)
德 >= H && 才 >= H 为第一类人“才德全尽”
德 >= H && 才 >= L && 才< H 为第二类人“德胜才”,才分不到德分到
德 < H && 德 >= L && 才 < H && 才 >= L && 德 >= 才为第三类人,“才德兼亡”但尚有“德胜才”
德 >= L && 才 >= L为第四类人,只达到最低线要求

  • 使用中的sort函数加快排序的速度

我们知道在库中有sort函数,sort函数有个参数(数组起始位置,数组终止位置),这是默认升序排列,如果要自己添加排序顺序可以加入第三个参数,排序方法。

//定义的排序方法函数
bool compare(data a, data b)//sort排序比较的第三个参数,true为排序前,false为后
{
   
   
	return a>b;
}
//调用方法
sort(data.begin(), data.end(), compare);

排序方法是bool类型的函数

  • 使用scanf函数而不是cin函数加快读取数据的速度(据说前者的速度是后者的十倍以上)

此题由于数据量很大,使用cin读取数据会运行时间超出规定范围,需要使用scanf函数

scanf("%d %d %d", &temp.id, &temp.virtue, &temp.gift);

代码:德才论

1016 部分A+B

题目:部分A+B

思路:此题比较简单,循环读取字符串中的每个字符,遇到相同的字符计数,根据计数返回一个整数类型的PA即可

代码:部分A+B

1017 A除以B

题目:A除以B

思路:由于题目要求计算不超过1000位的正整数,不能使用计算机的整数数据类型计算(范围不够),因此本题的解决方式是将人用纸笔计算除法的过程转换为程序,即:从最高位开始除,将余数记到下一位的进位当中,关键函数代码如下:

int div(char *a, int b)//a是被除数,b是除数,div是余数
//使用指针变量可以返回计算的除数的字符数组,同时还返回了余数
{
   
   
	int i = 0;
	int div = 0;
	int temp;
	while (a[i] != '\0')
	{
   
   
	//将每一位字符转化为数字加上上一位的余数*10然后做除法
		temp = (a[i] - '0' + div*10) % b;//记录余数,如果前面有余数,需要加上借位
		a[i] = '0' + (a[i] - '0'+div*10) / b;//记录除数
		div = temp;
		i++;
	}
	return div;
}

另外,还要注意的是,还要注意结果为0,或者首位为0的特殊情况

代码:A除以B

1018 锤子剪刀布

题目:锤子剪刀布

思路:本题比较简单,定义函数对胜负状况进行判断,根据胜负状况记录谁通过什么策略获胜,最后按照要求输出结果即可

代码:锤子剪刀布

1019 数字黑洞

题目:数字黑洞

思路:本题比较简单,定义一个函数对一个四位数进行到达数字黑洞的演算,将原数字每一位取出,存放到vector数组中,然后使用sort函数排序,将排序结构表示的大数与小数存储下来,返回相减结果,同时输出结果即可

代码:数字黑洞

1020 月饼

题目:月饼

思路:本题是一个背包问题,需要先建立结构体变量,用于记录月饼的库存,总价与单价,根据单价最大原则进行取,放入背包

struct mooncake
{
   
   
	double stocks;//月饼库存
	double total;//月饼总价
	double price;//月饼单价
};

排序的时候使用自定义的比较规则调用sort函数,与1015题类似:

//比较原则为单价大,排序考前
bool compare(mooncake a, mooncake b)//对sort函数定义比较方法
{
   
   
	return a.price > b.price;
}

注意这个题有几个测试点有陷阱
1:只说了市场最大需求量为整数D,但是库存售价只说是正数(存在小数的情况,因此需要使用double数据类型)
2:如果需求大于库存总量,只能输出所有的库存的总价,因为无法提供更多的月饼

代码:月饼

1021 个位数统计

题目:个位数统计

思路:这个题比较简单,将数字读取到字符串中,然后对字符串进行从0-9的字符查找计数即可,最后将计数不为零的结果输出即可

代码:个位数统计

1022 D进制的A+B

题目:D进制的A+B

思路:本题难度不大,主要是需要定义一个函数对十进制数进行转化,对十进制数转化的方法为对其进行求余运算,例如将56转化为7进制数,计算方法为:

56/7=8···0
8/7=1···1
1/7=0···1
直到除数结果为0,逆序记录余数,110即为56的7进制数

程序如下:

string tentoD(int number, int D)//根据进制数D将10进制数字转化为字符串
{
   
   
	if (number == 0) return "0";//本题有一个注意点是A,B都为0的情况要予以考虑,有一个测试用例就是对0的结果的输出
	vector<int> s;
	string n;
	while (number != 0)//进制数的转化,记录余数
	{
   
   
		s.push_back(number%D);
		number /= D;
	}
	int temp;
	while (!s.empty())
	{
   
   
		temp = *(s.end() - 1);//取最后一位数字,逆序即为转换后的字符串
		n +='0' + temp;
		s.pop_back();
	}
	return n;
}

定义好函数直接计算加和结果通过函数转化一下进制数即可。

注意本题有一个测试用例是A,B都为0,需要在函数中对0的情况加以考虑

另外,附上将D进制字符串转化为10进制数的方法:

//本函数需要include<stringstream>与<cmath>
int Dtoten(string a, int D)//字符串a是D进制数,需要将其转化为10进制数
{
   
   
	int number=0;
	for (int i = 0; i < a.size(); i++)
	{
   
   
		number += (a[i]-'0') * pow(D, a.size() - 1 - i);
	}
	return number;
}

代码:D进制的A+B

1023 组个最小数

题目:组个最小数

思路:本题比较简单,将0-9的个数存在一个长度为10的数组,先对下标为1-9的数组寻找,找到第一个个数不为0的数字,将其输出作为首位,同时对这个下标的数字个数减去1,最后对下标为0-9的数组按照个数输出序号即可

代码:组个最小数

1024 科学计数法

题目:科学计数法

思路:本题难度适中,思路是将字符一个一个读取出来,按照规则进行输出。

  1. 首先读第一个字符,如果是‘+’则不输出,若是‘-'则输出‘-’。
  2. 读符号E以前的字符,存在一个字符数组s中
  3. 读符号E以后的字符,存在一个整型n中

按照规定读好数据以后,就根据n的情况对字符数组表示的数字进行科学计数法的还原

  • 当n小于0,则需要将字符的小数点向前移动n位,由于小数点前只有一个数字,因此移动小数点就要在首位数字前增加-n个零同时把原来的小数点删除,在新的第二位增加小数点,例如+1.2E-3,需要在前面增加三个零变为0.0012,关键代码如下:
//s是存储字符数组的deque对象
s.erase(s.begin() + 1);//移除原来第二位的小数点
	for (int i = 0; i < -n; i++)
		s.push_front('0');//在数组前增加-n个零
s.insert(s.begin() + 1,'.');//在新的字符数组第二位加上小数点
  • 当n大于零的时候,需要分两类考虑:

一类是n小于s小数点以后的数字的个数,例如+1.23400E+03,小数点后有5个数字,而n=3,这时只要将小数点后移n位即可。操作如下:

//小数点的位置是s.begin()+1
s.insert(s.begin() + 1 + n + 1, '.');//在小数点后的n+1位置增加一个小数点
s.erase(s.begin() + 1);//删除原来的小数点
  • 第二类是n大于等于s小数点以后的数字的个数,例如+1.23400E+06或者+1.23400E+05,这时只要删除原来的小数点,然后在数组最后增加(n-小数点以后的数字的个数)个零,例如+1.23400E+06,n=6,小数点以后有5个数字,只要删除小数点,然后在后面加6-5=1个零变为1234000即可。操作如下:
//number=s.size()-2,是小数点以后的数字的个数
for (int i = 0; i < n - number; i++)
		s.push_back('0');//在后面追加n-number个0
s.erase(s.begin() + 1);//移除小数点

代码:科学计数法

1025 反转链表

题目:反转链表

思路:本题难度略高,有两个测试用例不好通过,一是大量的节点数据,查找超时的问题,二是不一定所有的节点都是有效节点,对于这种情况需要删除无效的节点
例如这种:

00100 6 2
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 77777
12309 2 33218

在这个测试用例中,节点5的下个地址是77777,但是并没有节点的地址是77777,因此在链表中5之后的节点就应该要删除,最后的结果应该是2-1-4-3-5
在这里插入图片描述

另外本题还需要注意的是:对于最后小于K的节点,是不需要逆序的。例如这个测试用例,最后剩两个节点5,6,个数是小于4的,因此5,6就不需要逆序,最后的结果应该是4-3-2-1-5-6

00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218

在这里插入图片描述

而解决查找速度的问题,本人使用了< algorithm >库中的find_if函数
关于find_if函数,以后如有机会可以详细介绍一下,在这里的关键代码如下:

先定义判断函数:

class judge//对find_if算法的第三个函数参数的定义
{
   
   
private:
	int address;//judge函数是对节点node a进行判断,判断其地址是不是int address
public:
	judge(int add) :address(add){
   
   }//必须对该类进行构造函数的定义,由于在find_if中,第三个参数需要传入一个变量add,需要判断该变量与node中的地址是否相同
	bool operator()(node a)//由于find_if第三个参数无法传入自己的node值,因此需要对操作符()进行重新定义,使其可以使用操作符来间接调用自己的值
	{
   
   
		return a.address == address;//判断judge(add),如果a与node a的address相同返回true
	}
};

可以直接调用函数查找下一个节点:

vector<node>::iterator it;//先定义指针
it = find_if(L.begin(), L.end(), judge(first));
//找到地址为first的节点

以上是该题的两个难点的解决方法。

最后总结一下整个程序的思路
在这里插入图片描述

代码:反转链表

1026 程序运行时间

题目:程序运行时间

思路:本题很简单,只要注意输出的格式,两位不足补0即可,同时注意四舍五入的情况

代码:程序运行时间

1027 打印沙漏

题目:打印沙漏

思路:此题思路比较简单,主要是有一个陷阱比较坑:不需要输出多余的空格!!!
(以后看到格式有问题的情况可以将题目的输出案例复制到记事本上,然后一个一个的对比字符)

题目的思路如下,首先观察沙漏的字符个数与行数的关系:
1行沙漏只有1个字符,2行沙漏有7个字符,3行有17个,4行有31个,可以得到一个公式:n行的沙漏有2n2-1个字符。

这样就可以根据给定的字符个数,找出最大的沙漏的行数。
这里我使用了一个count函数来计算这个行数row

int count(int n)//计算n个字符可组成的最大的沙漏的行数
{
   
   
	int count = 1;
	while (1)
	{
   
   
		if (n >= 2 * count*count - 1)count++;
		else return count - 1;
	}
}

得到行数以后,我们可以得到最多一行输出的字符的个数为length=2row-1。
对于沙漏的第i行来说,这一行要输出2i-1个字符,为了居中显示,需要在其前面输出一定的空格,空格数应该等于[length-(2i-1)]/2,这样先从i=row输出到i=1,在从i=2输出到i=row即可得到沙漏。

代码:打印沙漏

1028 人口普查

题目:人口普查

思路:此题略有难度,主要思路如下,定义struct结构体,存储每个人的名字与姓名。

struct person
{
   
   
	char name[6];
	int year, month, day;
};

由于有的人的日期显示超过200岁或者大于现在的日期,这些都是不合理的需要筛除。一开始我想的方法是分别将年月日进行对比:

bool judge(int year, int month, int day)//
//先比较年在不在范围
//然后比较两个特殊年份的月在不在范围
//最后比较两个特殊的年份,特殊的月份,日在不在范围
	if (year > 1814 && year < 2014) return true;
	else if ((year == 1814 && month>9) || (year == 2014 && month < 9))return true;
	else if ((year == 1814 && month == 9 && day >= 6) || (year == 2014 && month == 9 && day <= 6))return true;
	else return false;
}

后来发现后更好的方法,就是将年份月份日期进行计算,将三个数合为一个数birthday=10000year+100month+day,这样就可以直接进行比较:

struct person//定义新的结构体
{
   
   
	char name[6];
	int birthday;//birthday=10000year+100month+day
};

bool judge(int birthday)
{
   
   
	if (birthday < 18140906 || birthday>20140906) return false;
	else return true;
}

定义好筛除函数以后,首先找到第一个符合日期范围的人,将其同时设置为max与min:

	int N;//总共有N行数据
	cin >> N;
	char name[6];
	int year, month, day;//临时存放年月日
	int birthday;//通过年月日计算生日的8位数字
	person max, min;
	int n = 0;//定义n是为了计数,看找到哪一位了
	int count = 0;
	while (1)
	{
   
   
		scanf("%s %d/%d/%d", name, &year, &month, &day);//读取第一个人的名字
		birthday = year * 10000 + month * 100 + day;
		n++;
		if (judge(birthday))//找到一地个日期合理的人的生日
		{
   
   
			count++;
			break;
		}
		if (n == N)//注意这里!一开始没有写这个if函数,导致始终有一个测试用例运行超时,后来想到是这个while函数无法跳出循环,原因是给出的所有日期都不符合要求范围
		{
   
   
			printf("0");//这种情况需要输出一个0
			return 0;
		}
	}
	strcpy(max.name, name);//将其存在max与min中
	max.birthday = birthday;
	strcpy(min.name, name);
	min.birthday = birthday;

一开始这里犯了一个严重的错误,导致有一个测试用例始终显示超时,检查了很久才发现如果所有给的数据都是不符合要求的,这时候要跳出循环并输出0,不能等待一直输入。

for (int i = n; i < N; i++)//找到第一个满足条件的数据后,从后一位开始循环到最后
	{
   
   
		scanf("%s %d/%d/%d", name, &year, &month, &day);
		birthday = year * 10000 + month * 100 + day;
		if (judge(birthday))//以后一找到一个就计数
		{
   
   
			count++;
		}
		else continue;//如果没有找到继续循环
		if (max.birthday>birthday)//找到满足的数据,将其与max的生日进行对比,如果max生日比这个晚,将max的生日与姓名更改为新的这个人
		{
   
   
			strcpy(max.name, name);
			max.birthday = birthday;
		}
		if (min.birthday<birthday)//同上,看其与min的关系
		{
   
   
			strcpy(min.name, name);
			min.birthday = birthday;
		}
	}
	printf("%d %s %s", count, max.name, min.name);
	return 0;

代码:人口普查

1029 旧键盘

题目:旧键盘

思路:此题难度适中,思路是将两个句子的每个字符存到两个集合中(相同的元素集合中只能出现一次)

下面是将一个字符存入集合的操作,注意将小写转化为大写

	string sen1;
	set<char> s1;
	char temp;
	for (int i = 0; i < sen1.size(); i++)//将sen1中的每一个字符插入集合s1中
	{
   
   
		temp = sen1[i];
		if (temp >= 'a'&&temp <= 'z')temp += 'A' - 'a';//如果是小写字母,转化为大写字母
		s1.insert(temp);
	}

然后对两个集合求差找到那些坏键。

//对比两个集合,将s1中s2有的清除
	set<char>::iterator it,p;
	for (it = s2.begin(); it != s2.end(); it++)
	{
   
   
		for (p = s1.begin(); p != s1.end(); p++)
		{
   
   
			if (*it == *p)
			{
   
   
				s1.erase(p);
				break;
			}
		}
	}

由于题目要求按照输入顺序输出坏键,因此需要在原句子中找到坏键的序号

	for (int i = 0; i < sen1.size(); i++)//首先将sen1中所有的小写字母转化为大写字母
	{
   
   
		if (sen1[i] >= 'a'&&sen1[i] <= 'z')sen1[i] += 'A' - 'a';
	}
	vector<int> number;//存入序号
 	int tep;
	for (it = s1.begin(); it != s1.end(); it++)//对s1中剩下的字符一个个在sen1中查找其顺序(使用find函数),将序号存入number中
	{
   
   
		tep = sen1.find(*it);
		number.push_back(tep);
	}
	

最后根据序号的升序顺序输出对应的字符即可。

	sort(number.begin(), number.end());
	for (int i = 0; i < number.size(); i++)//最后输出这些序号代表的字符
	{
   
   
		cout << sen1[number[i]];
	}
	return 0;
}

代码:旧键盘

1030 完美数列

题目:完美数列

思路:本题难度略大(一开始的时候想加快速度,定义了一个数,last=max/p,想从最小数循环到last就可以了,减少了几个循环,结果导致最后一个测试用例始终通过不了)

个人猜测最后一个案例可能类似是这样的(就是包括p有很多大数的情况):

6 99999998
1 999999995 999999996 999999997 999999998 999999999

其实抛开这个测试点,整体的思路还是比较清晰的

首先读取所有的数存入数组中,然后使用sort进行排序:

	int N;
	double p;
	double n[100001];
	cin >> N >> p;
	for (int i = 0; i < N; i++)
	{
   
   
		scanf("%lf", &n[i]);
	}
	sort(n, n + N);

然后定义一个双循环,从小到大,计算每个数的完美序列的个数,将最大的存起来:

注意内循环查找的时候可以从i+max开始,可以大大减少查找时间(不加的话有一个测试用例会运行超时)

	int max = 1;//首先定max=1
	for (int i = 0; i < N; i++)//i从0开始循环
	{
   
   
		for (int j = i+max; j < N; j ++)//j从i+max开始判断可以减少很多不必要的判断,大大加快程序的运算速度
		{
   
   
			if (n[j] > p*n[i])//找到第一个比p*n[i]大的数的序号j
			{
   
   
				max = (j - i)>max ? (j - i) : max;//根据j与i的位置,计算这个数列的个数为j-i(因为j的前一个数字才满足完美数列要求,n[j]并不满足)
				break;
			}
			else if(n[j]==p*n[i]||j==N-1)//如果找到的是第一个等于p*n[i],计算数列个数的时候要+1
			//当找到最后一个数字都没有满足>=的情况的时候,证明i后面所有的数字都可以组成完美序列,也要停止
			{
   
   
				max = (j - i + 1) > max ? (j - i + 1) : max;
				break;
			}
		}
	}

最后输出max即可。

代码:完美数列

1031 查验身份证

题目:查验身份证

思路:这个题难度不高,对每个字符串前17位进行一个判断即可,不满足判断的情况就将其存起来

判断函数如下,注意要考虑检查前17个字符是否全为数字,不满足的需要直接给false

int weight[] = {
   
    7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2 };

char M[] = {
   
    '1','0','X','9','8','7','6','5','4','3','2'};

bool judge(string a)//判断一个字符串是否合理
{
   
   
	int sum = 0;
	for (int i = 0; i < 17; i++)
	{
   
   
		if (a[i]<'0' || a[i]>'9') return false;//注意要检查是否前17位全是数字
		sum += (a[i] - '0')*weight[i];//对前17位进行加权求和
	}
	sum = sum % 11;
	char last = M[sum];
	if (last == a[17])return true;
	else return false;
}

代码:查验身份证

1032 挖掘机技术哪家强

题目:挖掘机技术哪家强

思路:这个题目名字起得很风骚,但是题目却很简单,由于题目中学校的序号是从1开始连续编号,只要定义个长度为105的数组,对下标为序号的数字进行求和,然后找到总分最大的数字输出序号与总分即可。

代码:挖掘机技术哪家强

1033 旧键盘打字

题目:旧键盘打字

思路:本题难度不高,但是我却卡了很久(从2018年年末调bug调到了最后一秒,不过好歹也算在2018年的最后调好了(T_T)),最后发现是一个很蠢的失误,是由于在主函数外的函数中一个if后面没有else,导致在一些情况下会没有返回值,导致判断程序出错。这在编译的时候其实就提醒过我这个warning:C4715,但是我没有在意,导致我最后找了很久的错
在这里插入图片描述
这告诉我一个道理:

每一个if后都要规范书写加上else,就如同做人一样,不能只考虑if,还要给自己留一个else的后路

话不多说,说一下本题的思路,本题主要就是对第二个字符串如何判断的问题,这里我定义了一个judge判断函数:

bool judge(char s1, char s2)//这个函数用于比对两个字符,判断有无坏键,是否要输出,s1代表坏键
{
   
   
	if (s1 == s2) return false;//如果相同,代表是坏键,返回false
	if (s1 == '+' && (s2 >= 'A'&&s2 <= 'Z'))return false;//虽然不同,但是上档键坏了,所有大写字符不输出
	else if (s2 >= 'a'&&s2 <= 'z')//如果是小写字母,要判断他的大写字母是否是坏键
	{
   
   
		if (s2 + 'A' - 'a' == s1)	return false;
		else return true;//一开始就是这句语句没有写,导致提交的时候三个大的测试点不能通过,以后一定要注意每一个if后面都要加上else
	}
	else return true;
}

定义好判断函数后,定义双层循环,对第二个字符的每一个字符在第一个字符中循环判断,必须所有的判断结果都为true,最后才有可能输出这个字符

for (int i = 0; i < s2.size(); i++)//对比s2中的每一个字符,判断是否要输出
	{
   
   
		bool temp = true;
		for (int j = 0; j < s1.size(); j++)//将s2的字符与s1中每一个字符进行对比
		{
   
   
			temp = temp && judge(s1[j], s2[i]);//将所有逻辑判断结果求“与”,必须所有的是true最后结果才为true
		}
		if (temp)cout << s2[i];//所有判断都是true,可以输出
	}

注意本题还有一个测试

点听说比较坑,就是第一个字符为空的情况(这个我一开始就考虑了,所以没遇到这个测试点不通过),所以要用getline函数读取每一行的数据

代码:旧键盘打字

1034 有理数四则运算

题目:有理数四则运算

思路:本题难度较大,最好使用面向对象的编程方法,思路如下:

  • 首先构造一个分数类:

分数类包含的信息有:这个分子原始的分子分母,化简后的整数部分,化简后分子分母,最大公约数,符号信息

class fraction
{
   
   
private:
	long long a, b;//原始的分子分母
	long long mol;//分子
	long long den;//分母
	long long gcd;//最大公约数
	long long integer;//整数部分
	bool judge;//记录是否为负数,false为负数
	bool legal=true;//判断这个分数是否合法(分母为零不合法)
public:
	fraction(){
   
   };
	fraction(long long a, long long b) :a(a), b(b){
   
   ...};
	void set(long long a, long long b){
   
   ...}
	void print();
	fraction plus(fraction a);
	fraction sub(fraction a);
	fraction mul(fraction a);
	fraction div(fraction a);
};
  • 定义构造函数与初始化函数:
fraction(long long a, long long b) :a(a), b(b)
{
   
   
	judge = true;//记录是否为负数
	if (a < 0)
	{
   
   
		a = -a;
		judge = false;//false代表是负数
	}
	//一开始使用从2开始循环判断是否能整除来查找最大公约数,后来运行超时,说明该算法不合适
	//int g = 1;
	//for (int i = 2; i <= a && i <= b; i++)//计算最大公约数
	//{
   
   
	//	if ((b%i == 0) && (a%i == 0))g = i;
	//}
	//后来使用辗转相除法来寻找最大公约数
	//先找两个数中的较大的数
	long long max = a >= b ? a : b;
	long long min = a < b ? a : b;
	long long temp;
	while (min != 0)//计算max % min =c
	//然后max=min,min=c;继续计算,直到min等于0为止,max就是最大公约数
	{
   
   
		temp = max % min;	
		max = min;
		min = temp;
	}
	gcd = max;
	integer = a / b;//记录整数部分
	mol = (a-integer * b)/gcd;//记录去掉整数部分的分数部分
	den = b / gcd;//记录分母
};

set函数同理,但是set函数需要对分数是否合法判断

if (b == 0)
{
   
   
	legal = false;
	return;
}
  • 定义输出这个分数的函数:
void fraction::print()
{
   
   
	if (!legal)//首先判断是否合法,不合法的话输出Inf
	{
   
   
		cout << "Inf";
		return;
	}
	if (mol == 0 && integer == 0)//如果分子与整数部分都为零输出0
	{
   
   
		cout << 0;
		return;
	}
	if (judge)//如果不是负数不需要加括号
	{
   
   
		if (integer != 0)//如果整数部分不为零需要输出整数部分
		{
   
   
			if (mol == 0)//如果分子为零,输出完整数部分直接结束
			{
   
   
				cout << integer;
				return;
			}
			else//否则需要多输出一个空格
			{
   
   
				cout << integer << ' ';
			}
		}
		//输出分子与分母
		cout << mol << '/' << den;
		return;
	}
	else//是负数同上,只是在开始前需要加上'('与'-'号,最后return前需要加')'
	{
   
   
		cout << "(-";
		if (integer != 0)
		{
   
   
			if (mol == 0)
			{
   
   
				cout << integer << ')';
				return;
			}
			else
			{
   
   
				cout << integer << ' ';
			}
		}
		cout << mol << '/' << den << ')';
		return;
	}
}
  • 定义加减乘除运算:

加:

fraction fraction::plus(fraction a)
{
   
   
	fraction temp;
	long long mol, den;
	mol = this->a * a.b + a.a*this->b;
	den = this->b * a.b;
	temp.set(mol, den);
	return temp;
}

减:

fraction fraction::sub(fraction a)
{
   
   
	fraction temp;
	long long mol, den;
	mol = this->a * a.b - a.a*this->b;
	den = this->b * a.b;
	temp.set(mol, den)
### 关于PAT Basic Level Practice的测试点及题目解析 #### 题目难度分级 PAT(Programming Ability Test)是由浙江大学举办的计算机程序设计能力考试,分为不同级别。其中乙级Basic Level主要面向初学者,考察基本编程技能[^1]。 #### 测试点特点 对于PAT Basic Level中的某些特定题目而言,其测试点设置较为严格。例如,在处理字符串匹配类问题时,需要注意算法逻辑中何时应当终止循环以防止不必要的重复计算;而在涉及数值运算的问题里,则可能因为边界条件而增加复杂度[^3]。 #### 编程语言的选择影响 值得注意的是,尽管大部分简单题目可以作为学习某种新语言的良好实践材料,但在实际操作过程中可能会遇到由于所选语言特性而导致难以通过全部测试点的情况。比如Java在面对部分效率敏感型试题时表现不佳,这可能是由于该语言本身的执行速度相对较慢以及内存管理方式等因素造成的。因此有时不得不转而采用其他更适合解决此类问题的语言版本来完成解答[^2]。 ```cpp #include<bits/stdc++.h> using namespace std; int a[100000]; int c=1; void getPrime(){ int flag=0; for(int i=2;i<105000;i++){ flag=1; for(int j=2;j<=sqrt(i);j++){ if(i%j==0){ flag=0; break; } } if(flag==1) a[c++]=i; } } int main(){ int m,n,i,t=1; scanf("%d %d",&m,&n); getPrime(); for(i=m;i<=n;i++){ if(t%10==1){ printf("%d",a[i]); t++; }else{ printf(" %d",a[i]); t++; } if((t-1)%10==0) printf("\n"); } return 0; } ``` 上述C++代码展示了如何实现一个简单的质数打印功能,并且针对输出格式进行了特殊处理以满足特定要求。这段代码很好地体现了编写高效解决方案的重要性,尤其是在应对像PAT这样的在线评测系统时[^4]。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值