为了备考PAT考试,近期在刷PAT的习题,从易到难。
什么是PAT:
浙江大学计算机程序设计能力考试(Programming AbilityTest,简称PAT)是由浙江大学计算机科学与技术学院组织的统一考试。旨在培养和展现学生分析问题、解决问题和计算机程序设计的能力,科学评价计算机程序设计人才,并为企业选拔人才提供参考标准。
PAT乙级要求掌握的知识:
1.具备基本的C/C++的代码设计能力,掌握相关开发环境的基本调试技巧;
2.理解并掌握最基本的数据结构,如:线性表、树、图等;
3.理解并熟练编程实现与基本数据结构相关的基础算法,包括递归、排序、查找等;
4.学会分析算法的时间复杂度、空间复杂度和算法稳定性;
5.具备问题抽象和建模的初步能力,并能够用所学方法解决实际问题。
本博客用于记录在做PAT乙级题库的时候的新得。同时对一些题目的思路,不容易一次通过的测试要点也会进行记录
本文已经全部更新完毕
博客中涉及的代码均存储在我的GitHub仓库中,如有需要可以直接点击链接查看或下载:
PAT乙级题库
题目:害死人不偿命的(3n+1)猜想
思路:这个题很简单,通过对n简单的循环与判断即可实现
代码:害死人不偿命的(3n+1)猜想
题目:写出这个数
思路:需要将正整数存在字符串中,然后将每个字符转换为数字进行加和,最后将求和的数字中各个位数字取出来,按照顺序输出对应拼音即可
代码:写出这个数
题目:我要通过!
思路:需要定义一个函数判断什么样的字符串可以通过,关键在于对第三个条件,如何转化为一个表达式是关键
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,得到答案正确
}
代码:我要通过!
题目:成绩排名
思路:本题较简单,定义结构数组,使用冒泡排序即可(或者使用算法里的sort函数)
代码:成绩排名
题目:继续(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)猜想
题目:换个格式输出整数
思路:本题较简单,读取出百位,十位,个位后按照要求输出即可。
用 12…n 来表示不为零的个位数字 n(<10)
注意这句话的意思是从1开始循环输出,例如n是2,输出12,n是3,输出123,n是5输出12345
代码:换个格式输出整数
题目:素数对猜想
思路:本题难度不大,关键在于判断素数的程序运算效率要高,不然会出现运行超时
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;
}
代码:素数对猜想
题目:数组元素循环右移问题
思路:本题难度不大,要求不借助其他数组,可以使用deque队列,两端进行删除添加效率更高
代码:数组元素循环右移问题
题目:说反话
思路:使用队列往前段循环输入字符串,然后顺序输出即可
代码:说反话
题目:一元多项式求导
思路:定义两个数组,一个用于存放系数,一个存放指数,然后对系数乘以指数即为求导后的系数(分常数项与非常数项)
代码:一元多项式求导
题目:A+B 和 C
思路:此题问题很简单,但是要注意的是本题要求计算的整数范围为[-231,231],因此A+B的范围为[-232,232],已经超出C++中int数据类型的范围,因此如果使用int类型会有一些测试用例无法通过,应该使用长整型变量
C++中long与int的范围一样,使用long long类型
代码:A+B 和 C
题目:数字分类
思路:此题较简单,使用vector组对数字进行分类储存即可
代码:数字分类
题目:数素数
思路:此题较简单,通过动态数组存储数据,注意判断素数的程序尽量高效,详情参见1007题。
代码:数素数
题目:福尔摩斯的约会
思路:此题的主要陷阱在于判断的字符必须是范围内的数组数据,不属于范围内的字符相同是无效的。
//范围内的数组数据
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;
}
}
代码:福尔摩斯的约会
题目:德才论
思路:此题有三个关键点
根据题目要求划分人物:(注意是用if-else if的判断,前面判断不通过才进入后面的判断)
德 >= H && 才 >= H 为第一类人“才德全尽”
德 >= H && 才 >= L && 才< H 为第二类人“德胜才”,才分不到德分到
德 < H && 德 >= L && 才 < H && 才 >= L && 德 >= 才为第三类人,“才德兼亡”但尚有“德胜才”
德 >= L && 才 >= L为第四类人,只达到最低线要求
我们知道在库中有sort函数,sort函数有个参数(数组起始位置,数组终止位置),这是默认升序排列,如果要自己添加排序顺序可以加入第三个参数,排序方法。
//定义的排序方法函数
bool compare(data a, data b)//sort排序比较的第三个参数,true为排序前,false为后
{
return a>b;
}
//调用方法
sort(data.begin(), data.end(), compare);
排序方法是bool类型的函数
此题由于数据量很大,使用cin读取数据会运行时间超出规定范围,需要使用scanf函数
scanf("%d %d %d", &temp.id, &temp.virtue, &temp.gift);
代码:德才论
题目:部分A+B
思路:此题比较简单,循环读取字符串中的每个字符,遇到相同的字符计数,根据计数返回一个整数类型的PA即可
代码:部分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
题目:锤子剪刀布
思路:本题比较简单,定义函数对胜负状况进行判断,根据胜负状况记录谁通过什么策略获胜,最后按照要求输出结果即可
代码:锤子剪刀布
题目:数字黑洞
思路:本题比较简单,定义一个函数对一个四位数进行到达数字黑洞的演算,将原数字每一位取出,存放到vector数组中,然后使用sort函数排序,将排序结构表示的大数与小数存储下来,返回相减结果,同时输出结果即可
代码:数字黑洞
题目:月饼
思路:本题是一个背包问题,需要先建立结构体变量,用于记录月饼的库存,总价与单价,根据单价最大原则进行取,放入背包
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:如果需求大于库存总量,只能输出所有的库存的总价,因为无法提供更多的月饼
代码:月饼
题目:个位数统计
思路:这个题比较简单,将数字读取到字符串中,然后对字符串进行从0-9的字符查找计数即可,最后将计数不为零的结果输出即可
代码:个位数统计
题目: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与
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
题目:组个最小数
思路:本题比较简单,将0-9的个数存在一个长度为10的数组,先对下标为1-9的数组寻找,找到第一个个数不为0的数字,将其输出作为首位,同时对这个下标的数字个数减去1,最后对下标为0-9的数组按照个数输出序号即可
代码:组个最小数
题目:科学计数法
思路:本题难度适中,思路是将字符一个一个读取出来,按照规则进行输出。
按照规定读好数据以后,就根据n的情况对字符数组表示的数字进行科学计数法的还原
//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小于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);//删除原来的小数点
//number=s.size()-2,是小数点以后的数字的个数
for (int i = 0; i < n - number; i++)
s.push_back('0');//在后面追加n-number个0
s.erase(s.begin() + 1);//移除小数点
代码:科学计数法
题目:反转链表
思路:本题难度略高,有两个测试用例不好通过,一是大量的节点数据,查找超时的问题,二是不一定所有的节点都是有效节点,对于这种情况需要删除无效的节点
例如这种:
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的节点
以上是该题的两个难点的解决方法。
代码:反转链表
题目:程序运行时间
思路:本题很简单,只要注意输出的格式,两位不足补0即可,同时注意四舍五入的情况
代码:程序运行时间
题目:打印沙漏
思路:此题思路比较简单,主要是有一个陷阱比较坑:不需要输出多余的空格!!!
(以后看到格式有问题的情况可以将题目的输出案例复制到记事本上,然后一个一个的对比字符)
题目的思路如下,首先观察沙漏的字符个数与行数的关系:
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即可得到沙漏。
代码:打印沙漏
题目:人口普查
思路:此题略有难度,主要思路如下,定义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;
代码:人口普查
题目:旧键盘
思路:此题难度适中,思路是将两个句子的每个字符存到两个集合中(相同的元素集合中只能出现一次)
下面是将一个字符存入集合的操作,注意将小写转化为大写
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;
}
代码:旧键盘
题目:完美数列
思路:本题难度略大(一开始的时候想加快速度,定义了一个数,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即可。
代码:完美数列
题目:查验身份证
思路:这个题难度不高,对每个字符串前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;
}
代码:查验身份证
题目:挖掘机技术哪家强
思路:这个题目名字起得很风骚,但是题目却很简单,由于题目中学校的序号是从1开始连续编号,只要定义个长度为105的数组,对下标为序号的数字进行求和,然后找到总分最大的数字输出序号与总分即可。
代码:挖掘机技术哪家强
题目:旧键盘打字
思路:本题难度不高,但是我却卡了很久(从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函数读取每一行的数据
代码:旧键盘打字
题目:有理数四则运算
思路:本题难度较大,最好使用面向对象的编程方法,思路如下:
分数类包含的信息有:这个分子原始的分子分母,化简后的整数部分,化简后分子分母,最大公约数,符号信息
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);
return temp;
}
乘:
fraction fraction::mul(fraction a)
{
fraction temp;
long long mol, den;
mol = this->a * a.a;
den = this->b * a.b;
temp.set(mol, den);
return temp;
}
除:
fraction fraction::div(fraction a)//由于除法其实是乘倒数,因此要分除数是正数还是负数分别考虑(颠倒以后负号会跑到分母)
{
fraction temp;
long long mol, den;
if (a.judge)
{
mol = this->a * a.b;
den = this->b * a.a;
}
else
{
mol = -1* this->a * a.b;
den = -1* this->b * a.a;
}
temp.set(mol, den);
return temp;
}
主函数:
int main()
{
long long a1, b1, a2, b2;
scanf("%lld/%lld %lld/%lld", &a1, &b1, &a2, &b2);
fraction a(a1, b1);
fraction b(a2, b2);
a.print(); cout << " + "; b.print(); cout << " = "; a.plus(b).print(); cout << endl;
a.print(); cout << " - "; b.print(); cout << " = "; a.sub(b).print(); cout << endl;
a.print(); cout << " * "; b.print(); cout << " = "; a.mul(b).print(); cout << endl;
a.print(); cout << " / "; b.print(); cout << " = "; a.div(b).print(); cout << endl;
return 0;
}
通过这种面向对象的编程思路,做起来很方便,缺点就是编写比较耗费时间,但是每一块逻辑会很清楚。
题目的测试点有两个坑:
代码:有理数四则运算
题目:插入与归并
思路:本题的难度较大,一开始以为插入排序稍微简单一点,但是最后发现两个算法都有各自的难点,要全部AC通过这个题,需要对两个排序方法有着比较好的理解。
插入排序的思路是,先找到需要插入的元素,然后找到第一个有序数列的终点,然后将待插入的元素插入这个有序数列中,而有序数列后面的东西都不能改变
下面是对一次插入排序的过程书写,首先找到需要插入的元素,然后将其值存储起来后从数组中删除,接下来找到需要第一个有序数字的终点,从数组起点到这个终点中进行插入操作
vector<int> insertion(vector<int> a)
{
vector<int> temp = a;
vector<int>::iterator find = temp.begin();
unsigned int i;
for (i = 0; i < temp.size()-1; i++)//找到需要插入的元素
{
if (temp[0] > temp[1])//如果第一个元素就是就是乱序,则需要插入的元素就是这个元素
{
find = temp.begin();
break;
}
if (temp[i] > temp[i + 1])//如果后面的元素找到一个乱序,则乱序后一个元素就是需要插入的元素(注意这里是大于号,不是大于等于号,第四个测试点一直没通过就是这里的问题)
{
find = temp.begin() + i + 1;
break;
}
}
if (i == temp.size() - 1)return temp;//如果没找到需要插入的序列证明整个序列有序
int find_value = *find;//记录需要插入的元素的值
temp.erase(find);//将这个元素删除
//接来下就是要找到插入的位置,即在有序数列中间进行插入,因此先找到有序数列的最后一个元素的位置
vector<int>::iterator it;
for (it = temp.begin(); it < temp.end()-1; it++)//it前是一个有序数列(包括it)
{
if (*it > *(it+1))
{
break;
}
}
//cout<<*it<
//从数列开始到有序数列的最后一个开始查找插入位置
for (vector<int>::iterator j = temp.begin(); j <= it; j++)
{
if (*j > find_value)//若插入的元素小于遍历的值,在这个位置前插入元素
{
temp.insert(j, find_value);
return temp;
}
}
//如果找到最后一个元素还没有找到比需要插入的值大的元素,证明这个要插入的元素比有序数列的最大值还大,需要插入有序数列的尾端
temp.insert(it + 1, find_value);
return temp;
}
归并排序的思路是:想将数组一一分组,然后相邻两组进行归并排序,直到所有数组都归并完成,接下来扩大数组的长度,两两分组,然后相邻数组归并排序,直到所有数组归并完成,重复这样的操作直到最后只剩下一个有序数组。
例如,数组长度为13,先2,2,2,2,2,2,1分好组并排序,这样每一组都是有序的了,然后再4,4,4,1分好组并排序,然后再8,5归并,最后13归并。
再例如数组长度为7,分为2,2,2,1归并,然后分为4,3归并,最后7归并;
因此归并的实现需要一个全局变量length,从1开始每次进行一次归并length*=2,通过这样的操作实现归并
int length = 1;//需要定义一个全局变量(每次归并的集合的元素的个数)
vector<int> merge(vector<int> a)
{
vector<int> temp = a;
unsigned int i = 0;//定义i为第一个有序数列的开始
unsigned int j = i + 2 * length;//定义j为需要归并的第二个有序数列的结尾
if (j >= temp.size())//如果第二个有序数列的结尾超过界限,合并所有
{
sort(temp.begin()+i, temp.end());
return temp;
}
//否则依次归并两两相邻的集合,知道最后剩下一个集合位置
while (1)
{
if (j > temp.size())j = temp.size();//如果第二个有序数列的结尾超出界限,将其设置为最大值
sort(temp.begin() + i, temp.begin() + j);//从第一个有序数列的开始排序到第二个集合的结尾,这样就是归并了两个相邻的有序数列
i = j;//i位置向后递归
if(i==temp.size()) break;//直到第一个有序数列的开始到达界限点
j += 2 * length;//j位置向后递归
}
length *= 2;//一次递归结束后下一次的有序数列的长度扩大一倍
return temp;
}
最后在主函数中每次调用这两个函数来比较运算结果与输入是否相同即可。
代码:插入与归并
题目:跟奥巴马一起编程
思路:本题很简单,只要注意行数是列数的一半并要四舍五入即可
代码:跟奥巴马一起编程
题目:在霍格沃茨找零钱
思路:本题难度不大,主要思路是计算减法前将所有货币转化为knut进行计算即可,最后根据knut的大小输出需要找的零钱
其中关键的两个函数,将货币计算为knut的大小
int gallen_to_knut(int g, int s, int k)//将原货币转化为全部是knut的格式
{
int sum = 0;
sum = g * 17 * 29 + s * 29 + k;
return sum;
}
和将knut的格式输出为Galleon.Sickle.Knut的格式(注意负数的情况与等于零的情况即可)
void knut_to_gallen(int k)//输出knut可兑换的最大的galleon与sickle
{
bool judge = k >= 0;//首先判断knut是否大于等于0,注意等于零输出应该是0.0.0而不是0,这是最后一个测试用例的坑
if (!judge) k = -k;//如果是小于零的情况,记录下来同时将knut转化为大于零的数
//分别计算可兑换的galleon与sickle
int g = k / 17 / 29;
int s = (k - g * 17 * 29) / 29;
k = k - g * 17 * 29 - s * 29;
if (!judge)cout << '-';//如果小于零记得要输出一个负号
cout << g << '.' << s << '.' << k;
return;
}
代码:在霍格沃茨找零钱
题目:统计同成绩学生
思路:本题题目很简单,但是如果方法不对最后一个测试用例是不可能通过的,一开始我想的方法是定义数组,先将分数存进去,然后将数组排好序,对每一个需要查找的分数进行查找,前三个测试用例很简单就通过了,但是最后一个总是运行超时,一开始想的是排序的方法是不太慢了,更换了快速排序依旧运行超时,后来想的是,查找到一个就删除一个元素,这样以便后面查找更快,但是还是运行超时。苦思冥想下,突然想到,可以直接定义一个101个元素的整型数组,下标对应了分数,直接对这个数组进行统计,然后直接对下标进行查找即可。代码如下:
#include
using namespace std;
int main()
{
int N;
int score[101] = { 0 };//定义一个存放0-100分的的数组,下标0-100代表着得该分的人的个数
int temp;
cin >> N;
for (int i = 0; i < N; i++)
{
scanf("%d", &temp);//根据分数对下标为该分数的人的个数加1
score[temp]++;
}
int K;
cin >> K;
for (int i = 0; i < K-1; i++)
{
scanf("%d", &temp);//读取下标数据,输出该分数有多少人
cout << score[temp] << ' ';
}
scanf("%d", &temp);
cout << score[temp] << endl;
return 0;
}
附上一开始只通过前三个测试用例的代码:
#include
#include
#include
using namespace std;
int find_key(vector<int> a,int key)//定义查找函数对已经排好序的数组进行查找
{
vector<int>::iterator it;
it=find(a.begin(),a.end(),key);//找到第一个满足要求的成绩
if (it == a.end())return 0;//如果没有找到,证明该分数的人为0
else
{
int count = 0;
for (vector<int> ::iterator p = it; *p == key; p++)//从找到的第一个人开始往后,直到分数不同为止,进行计数
{
count++;
}
return count;
}
}
int main()
{
int N;
cin >> N;
vector<int> score;
int temp;
for (int i = 0; i < N; i++)
{
cin >> temp;
score.push_back(temp);
}
sort(score.begin(), score.end());
int K;
cin >> K;
vector<int> key;
for (int i = 0; i < K; i++)
{
cin >> temp;
key.push_back(temp);
}
int i;
for ( i= 0; i < K - 1; i++)
{
cout << find_key(score, key[i]) << ' ';
}
cout << find_key(score, key[i]) << endl;
}
代码:统计同成绩学生
题目:到底买不买
思路:本题比较简单,只要采取将所有字符的个数存在两个整型数组中,然后对比两个整型数组的差值即可
代码:到底买不买
题目:有几个PAT
思路:本题难度较大,但是不是编程上的难度,而是算法上的难度,一开始想的是用三重循环,一个一个统计PAT的个数,但是这样的算法后面的几个测试用例都会超时,最后实在是想不出来什么好的方法,借鉴网上大神的方法,豁然开朗,只要对每一个A的前后进行P与T的计数,用(A前P的数量)*(A后T的数量),即可得到由此A可以构建的所有PAT的数量,最后累加即可,整个代码也很简洁。
#include
#include
using namespace std;
int main()
{
string pat;
cin >> pat;
long long count = 0, count_p = 0, count_t = 0;//注意要使用long long类型,不然乘法的时候有可能越界
for (int i = 0; i < pat.size(); i++)//首先计算总共有多少个T
{
if (pat[i] == 'T')count_t++;
}
for (int i = 0; i < pat.size(); i++)//对每一个A进行遍历,计算前面多少个P,后面多少个T
{
if (pat[i] == 'P')count_p++;//前面有P就增加
if (pat[i] == 'T')count_t--;//前面有T证明后面的T的个数要减少
if (pat[i] == 'A')count += count_p*count_t;//A可以构建的所有PAT的计数
}
cout << count % 1000000007;//总的个数需要对1000000007取余数
return 0;
}
代码:有几个PAT
题目:考试座位号
思路:本题很简单,设置以试机座位号为下标的结构体数组后,根据下标进行搜索即可
代码:考试座位号
题目:字符统计
思路:本题较为简单,只需要定义一个长度为26的整数数组,存放字母字符出现的次数,从中找出最大的即可
代码:字符统计
题目:输出PATest
思路:本题较为简单,首先需要统计出所有字符出现的次数,然后根据顺序输出即可(每输出一次,该字符的个数减少1,直到所有字符个数为零为止停止循环)
代码:输出PATest
题目:火星数字
思路:本题难点主要在于判断输入的数据是什么类型的,是整数还是单词,是一个单词还是两个单词,根据不同的类型输出不同的结果。
主要的循环函数如下:
string temp1, temp2;
for (int i = 0; i < N; i++)
{
cin >> temp1;//首先读入一个存在字符串中
if (cin.peek() != '\n')//如果读了两个字符串,那么一定是火星文
{
cin >> temp2;
print(temp1, temp2);
}
else//如果读了一个字符,需要判断读入的是数字还是一个单词
{
if (temp1[0] >= '0'&&temp1[0] <= '9')//如果读入的是数字,将其转化为整型数
{
int number = stoi(temp1);
print(number);
}
else//如果读入的是单词,将其直接输出
{
print(temp1);
}
}
}
接下来需要定义一个三重重载的print函数,分别对这三种情况进行输出
void print(int n){...}//如果读入的是数字,将其转化为13进制数以后输出火星文
void print(string a){...}//如果只输入了一个字符,且是火星文
void print(string a, string b){...}//如果读入两个字符串,证明是火星文
这样这个题就解决了。
代码:火星数字
题目:快速排序
思路:本题难度稍大,一开始使用暴力循环方法,果然只能通过三个测试用例。
后来需要观察主元的特性:
如果一个元素是主元,首先必须满足:
(1)该元素的位置是正常排序的位置
(2)该元素前的最大元素必须比该元素小。
例如数列:1 3 2 4 5
主元1 4 5的位置与正常排序时1 2 3 4 5的位置是相同的,其次,每一个主元的前面都没有比他大的元素
例如数列: 5 4 3 1 2
数字3虽然与正常排序的位置相同,但是其前面有比他大的元素,因此3不是主元
知道这个规律以后就好处理了:
关键的判断循环如下:
for (int i = 0; i < N; i++)
//要满足为主元,首先需要该元素的位置为排序后的位置
//其次,该元素前的最大元素必须比这个元素小
{
max = max > store[i] ? max : store[i];//随时存储最大元素
if (store[i] != tep[i])tep[i] = 0;//如果位置不对,将该元素设为0
if (max > store[i])tep[i] = 0;//如果前面最大元素比该元素大,将该元素设置为0
if (tep[i] != 0)result.push_back(tep[i]);//如果该元素没有设为0,证明其是主元,存在result中
}
//其中tep数组是排序后的数组,store是排序前的数组
注意本题还有一个测试点,是如果没有主元需要先输出0,然后输出一个空行。
代码:快速排序
题目:划拳
思路:本题很简单,无序赘述,代码链接如下。
代码:划拳
题目:编程团体赛
思路:本题比较简单,三个数据中第二个数据,队员编号其实是没有用的,将属于相同队伍的人的成绩统计在一个长度为1001的数组中即可。
代码:编程团体赛
题目:数字加密
思路:本题难度不大,但是有一个很坑的点在于如果A的长度大于B,对于B前没有的位数,需要用0来进行运算。
因此本题将字符串存在两个string中,对其进行逆序的比较运算即可。
其中需要定义一个计算函数:
char cal(char a, char b, int n)//根据奇数偶数位定义不同的运算规则
{
char c;
int number;
if (n % 2 == 0)//如果n是偶数位
{
number = (b - '0') - (a - '0');
number = number >= 0 ? number : number + 10;
c = '0' + number;
return c;
}
else//如果n是奇数位
{
number = (b - '0') + (a - '0');
number %= 13;
switch (number)
{
case 10:
c = 'J';
break;
case 11:
c = 'Q';
break;
case 12:
c = 'K';
break;
default:
c = number + '0';
break;
}
return c;
}
}
循环时候,根据A与B的长度大小关系分两种情况即可,如果B长,对于B高位数字直接输出即可。如果A长,需要对B没有的高位数字进行补0然后运算。
例如:
A=1234567 B=368782971:B高位的36直接输出
A=368782971 B=1234567:B缺2个高位数字,将B改为001234567运算即可
代码:数字加密
题目:数列的片段和
思路:本题的代码很短,但是本题的思路却不是很好想,首先,使用多重循环的方法是绝对不可能AC所有测试点,尤其第三个测试点。因此就要想别的思路,我们可以观察一下,不同位置的元素会出现在多少个片段中:
假设有四个元素,我们会发现,1号位置元素总共出现在4个片段中,2号位置元素出现在6个片段中,3号位置元素出现在6个片段中,4号位置元素出现在4个片段中。这其中是暗含了一定的规律,因此我们在加和的时候只要知道每一个元素总共会出现在几个片段中即可进行一次循环的加和操作。
接下来我们研究一下不同位置元素会出现的次数的规律:
这个图我们可以看到第i个元素跟前面的所有元素可以组成i个不同的片段。
然后我们取其中一个片段,跟后面的元素结合,每一个片段就可以多产生(N-i)个片段
所以在数组中,我们只需要将i位置的元素乘以这个次数然后加和即可,这样一次循环就可以完成计算,代码如下:
#include
using namespace std;
int main()
{
int N;
cin >> N;
double sum = 0,temp;
for (int i = 0; i < N; i++)//由于数组下标是0开始的,因此需要转化一下
{
cin >> temp;
sum += temp*(i + 1)*(N - i);
}
printf("%.2f\n", sum);
return 0;
}
代码:数列的片段和
题目:螺旋矩阵
思路:本题较为简单,首先确定好矩阵的行列,由于要求m-n最小,因此我们从sqrt(N)向上查找,找到第一个能整除的整数就是m
int cal_m(int N)//计算矩阵的行数
{
int m;
m = int(sqrt(N));
if (m - sqrt(N)==0)return m;//首先要判断开方的数字是不是整数,如果是整数证明是一个正方形矩阵
else m++;
while (N % m != 0)
{
m++;
}
return m;
}
接下来我们将所有的数存储在数组中,通过sort进行排序
int N;
cin >> N;
int m = cal_m(N);
int n = N / m;
vector<int> store(N);
for (int i = 0; i<N; i++)//存储数据
{
cin >> store[i];
}
sort(store.begin(),store.end());
接下来我们需要定义四个边界,以及一个m*n的二维数组,通过下标i,j对这个矩阵进行螺旋填充
//这个是定义二维数组的方法
int **matrix=new int *[m];
for (int i = 0; i < m; i++)
{
matrix[i] = new int[n];
}
填充的时候按照右→下→左→上→右…的熟悉一直循环填充,直到下一次的填充达到边界为止退出
//定义四个边界以及i,j对矩阵进行填充,k来定位储存数据的位置
int up = -1, down = m, left = -1, right = n;//定义四个边界
int i = 0, j = 0, k = N-1;
while (1)
{
//----------------------向右填充----------------------------------------------
for (; j < right; j++)
{
matrix[i][j] = store[k];
k--;
}
up++;//向右填充完,上边界增加1
if (down == i + 1)break;//接下来向下填充,如果下边界只比i大1,证明填充完毕
else { i++; j--;}//否则将坐标移动到下一个要填充的位置
//---------------------向下填充-----------------------------------------------
for (; i < down; i++)
{
matrix[i][j] = store[k];
k--;
}
right--;//向下填充完,右边界减少1
if (left == j-1)break;//接下来向左填充,如果左边界只比j小1,证明填充完毕
else { j--; i--; }//否则将坐标移动到下一个要填充的位置
//---------------------向左填充-----------------------------------------------
for (; j > left; j--)
{
matrix[i][j] = store[k];
k--;
}
down--;//向左填充完,下边界减少1
if (up == i - 1)break;//接下来向上填充,如果上边界只比i小1,证明填充完毕
else {i--; j++;}//否则将坐标移动到下一个要填充的位置
//---------------------向上填充-----------------------------------------------
for (; i > up; i--)//向上填充
{
matrix[i][j] = store[k];
k--;
}
left++;//向上填充完,左边界增加1
if (right == j + 1)break;//接下来向右填充,如果右边界只比j大1,证明填充完毕
else { j++; i++; }//否则将坐标移动到下一个要填充的位置
}
填充完毕就输出就可以了:
for (i = 0; i < m; i++)
{
for (j = 0; j < n-1; j++)
{
cout << matrix[i][j] << ' ';
}
cout << matrix[i][j] << endl;
}
代码:螺旋矩阵
题目:复数乘法
思路:本题很简单,但是一开始后两个测试用例一直不能通过,后来网上查了原因,原来是C语言prinf在四舍五入的时候的问题:
例如在下面的代码中,本来四舍五入应该为0.00,但是由于n是负数,所以在printf中把负号保留了
知道了这一点,我们在输出的时候就需要对实部与虚部在(-0.005,0)这个区间内的数进行特殊处理:
代码如下:
#include
#include
using namespace std;
int main()
{
double R1, P1, R2, P2, R, P;//存储复数
cin >> R1 >> P1 >> R2 >> P2;
//计算复数的乘积
R = R1*R2;
P = P1 + P2;
double real, img;
real = R*cos(P);
img = R*sin(P);
//本题最坑的点在于-0.005到0的小数如果四舍五入按理说应该为0.00但是按照printf的方法输出的是-0.00,因此需要特殊考虑
if (real<0 && real > -0.005)printf("0.00");
else printf("%.2f", real);
if (img >=0)printf("+%.2fi\n",img);
else if (img > -0.005)printf("+0.00i");
else printf("-%.2fi\n",-img);
return 0;
}
代码:复数乘法
题目:卖个萌
思路:本题难度在于如何存储特殊的表情符号,由于一开始审题不细致,没有看到题目说:每个符号包含 1 到 4 个非空字符。单纯使用char字符存储,发现没法读入,后来使用string字符串就可以存储了。
在这里最关键的代码是如何读取正确的表情符号:
string s;
vector<string> hand, eye, mouth;
//分三段读取手,眼,嘴的表情字符
getline(cin,s);//注意要使用getline函数来读取正行
for (int i = 0; i < s.length(); i++)
{
if (s[i] == '[')//找到第一个'['符号
{
int j;
for (j = i + 1; s[j] != ']'; j++);//找到第二个']'符号
hand.push_back(s.substr(i + 1, j - i - 1));//将中间的字符存在字符串数组中
i = j;
}
}
其中两个重要的函数是:getline函数与substr函数
将表情符号存储后就需要根据数字要求输出:
for (int i = 0; i < K; i++)
{
cin >> lh >> le >> m >> re >> rh;
//注意如果数字的范围不在其中,输出"Are you kidding me? @\\/@",其中\需要打两个才能表示,这是转义符
//另外一开始只定了上界,没有规定不能<=0,所以导致第二个测试用例一直不能通过,猜测第二个测试用例中有数字0
if (lh > hand.size() || lh <= 0 || le>eye.size() || le <= 0 || m > mouth.size() || m <= 0 || re > eye.size() || re <= 0 || rh > hand.size() || rh <= 0)
{
cout << "Are you kidding me? @\\/@" << endl;
}
else
{
cout << hand[lh-1] << '('<<eye[le-1] << mouth[m-1] << eye[re-1] <<')'<< hand[rh-1] << endl;
}
}
代码:卖个萌
题目:住房空置率
思路:本题很简单,最后注意printf函数中如果要打%,需要打两个%%
代码:住房空置率
题目:求平均值
思路:本题思路很简单,只要对数字进行判断即可,重要的判断函数如下:
首先对首位是否为负号与数字进行判断,其次对中间各位是否为数字进行判断,同时记录小数点的个数是否为1,以及小数点的位置,最后对小数点后面的数字的个数进行判断。
bool judge(string a)
{
int point = 0, pos = -1, sub = 0;
//先判断第一个字符是否是数字与负号
if (!(a[0] == '-' || (a[0] >= '0'&&a[0] <= '9'))) return false;
for (unsigned int i = 1; i < a.length(); i++)//判断后面是否全是数字或者只有一个小数点
{
if (a[i] >= '0'&&a[i] <= '9')
{
continue;
}
else if (point == 0 && a[i] == '.')
{
point++;
pos = i;//记录小数点位置
continue;
}
else
return false;
}
//判断有没有超出两位小数
unsigned length = a.length() - pos - 1;//计算小数点到最后一位的长度
if (pos!=-1&&length>2)return false;
else return true;
}
判断通过的字符串转化为double类型后,再对其是否在[-1000,1000]区间进行判断,都判断通过的进行加和,不通过的输出错误。
stringstream ss;
cin >> N;
for (int i = 0; i < N; i++)
{
cin >> store;
if (judge(store))//先判断是否是合法数字
{
ss << store;
ss >> temp;
ss.clear();
if (temp >= -1000 && temp <= 1000)//如果是合法数字,将其转化为double后判断是否在[-1000,1000]的范围内
{
avg += temp;
legal++;
}
else
{
cout << "ERROR: " << store << " is not a legal number" << endl;
}
}
else
{
cout << "ERROR: " << store << " is not a legal number" << endl;
}
}
最后输出结果,注意当K=1,numbers应该改为number!
avg /= legal;
if (legal == 0)
{
cout << "The average of 0 numbers is Undefined" << endl;
}
else if (legal==1)//注意k=1的时候,number没有s!
{
cout << "The average of " << legal << " number is ";
printf("%.2f\n", avg);
}
else
{
cout << "The average of " << legal << " numbers is ";
printf("%.2f\n", avg);
}
代码:求平均值
题目:集体照
思路:本题难度适中,首先使用sort排序将不同人进行排序,其中第三个参数是是bool类型的函数(之前有提及过sort函数的第三个参数,可以看1015题德才论)
bool compare(person a, person b)//规定排序方法
{
//一开始是只想着比较名字的第一个字母,发现后两个测试点不过,后来想起来string可以直接进行大小的比较,是按照字符序的
return a.height != b.height ? a.height < b.height : a.name > b.name;
}
排序过程如下:
int N,K;//总人数
cin >> N >> K;
vector<person> store(N);//存储原始数据
for (int i = 0; i < N; i++)
{
cin >> store[i].name >> store[i].height;
}
sort(store.begin(), store.end(), compare);
之后需要按照顺序输出,这里的思路是使用deque,先将每一行中身高最高的放入队列中,然后按照依次将接下来的身高按照一前一后的顺序插入到队列中,例如一行中人的顺序为1 2 3 4 5 6,先将6放入队列,然后将5插到队列前,将4插入队列后,变为 5 6 4,依次进行这样的操作直到所有元素取完队列变为: 1 3 5 6 4 2,代码如下:
deque<string> *que = new deque<string>[K];//定义二维数组
int k,flag=1;//k定位,flag控制左右
for (int i = 0; i < K - 1; i++)//对前K-1行的处理
{
k = (i + 1)*(N / K) - 1;//定位到每一行最高的人的位置
que[i].push_back(store[k--].name);//先将最高的人放在数组中
for (int j = 0; j < N / K - 1; j++)
{
if (flag==1)
{
que[i].push_front(store[k--].name);//从前面插入
flag *= -1;//改变下一次方向
}
else
{
que[i].push_back(store[k--].name);//从后面插入
flag *= -1;
}
}
flag = 1;
//注意!这里一定要将flag改为1,不然之后会出错,第三个测试点就是这里一开始一直没法通过
}
注意对最后一行由于元素个数为:N-(K-1)*(N/K)个,需要单独处理:
k = store.size() - 1;
que[K - 1].push_back(store[k--].name);
for (int j = 0; j < N-(K-1)*(N/K)-1; j++)
{
if (flag==1)
{
que[K-1].push_front(store[k--].name);//从前面插入
flag *= -1;//改变下一次方向
}
else
{
que[K-1].push_back(store[k--].name);//从后面插入
flag *= -1;
}
}
最后倒序输出结果即可:
//最后输出站队结果
for (int i = K-1; i >=0; i--)
{
for (int j = 0; j < que[i].size() - 1; j++)
{
cout << que[i][j] << ' ';
}
cout << que[i][que[i].size() - 1] << endl;
}
代码:集体照
题目:组合数的和
思路:本题很简单,只要知道每一个数字会出现N-1次就可以了,代码链接如下
代码:组合数的和
题目:数零壹
思路:本题很简单,通过getline函数将字符串读入,然后根据是否是字母对其进行序号的求和,之后计算和的二进制数的零一个数即可,计算零一个数的函数如下:
struct result//统计最后一个数的二进制数的零一的个数的结构体
{
int zero=0;
int one=0;
};
result cal(int n)//统计n的二进制数的零一个数
{
result a;
while (n)
{
if (n % 2 == 1)a.one++;
else a.zero++;
n /= 2;
}
return a;
}
代码:数零壹
题目:选择题
思路:本题题目要求比较复杂,但是题目难度不是很高,注意几个要点:合理得读取序号,注意选项个数一定要相同(最后一个测试点就包括了选项虽少,但是选的都对的情况,由于没有选完所有情况,因此还是要判错)
首先建立储存问题信息的结构体:
struct problem
{
int pn;//问题序号
int score;//问题满分
int choice;//问题选项个数
vector<char> right;//问题正确选项
int person=0;//答错该题的人的个数
};
然后读取每一个问题的信息:
int N, M;//学生人数与多选题个数
cin >> N >> M;
problem *p = new problem[M];//存储题目信息的动态数组
int temp;//临时存储正确选项个数
char tep;//临时存储选项信息
for (int i = 0; i < M; i++)
{
p[i].pn = i + 1;//问题序号
cin >> p[i].score >> p[i].choice >> temp;//读入题目满分,所有选项个数,正确选项个数
for (int j = 0; j < temp; j++)
{
cin >> tep;//读入所有正确选项
p[i].right.push_back(tep);//将正确选项存入数组
}
}
判断每一个同学的答题情况,同时输出该学生的得分情况:
注意这里对于选项个数不对的情况需要直接判错:例如一个题的正确选项是a b,如果输入的是(1 a)或者(3 a b c)都要直接判为错(一开始忘了这一句,导致最后一个测试用例始终无法通过)
int c;//学生选项个数
for (int i = 0; i < N; i++)
{
int sum = 0;//记录该学生得分
for (int j = 0; j < M; j++)//判断每一个选项是否正确
{
int pos=0;//定位选项位置
bool judge = true;//判断学生答题正确与否
cin >> tep;//去掉第一个括号
cin >> c;
if (c !=p[j].right.size())judge = false;//!!!如果选项个数都不符合,一定是错的(防止有选项虽少,但是选的全对的情况给true)
for (int k = 0; k < c; k++)
{
cin >> tep;
if (judge && pos<p[j].right.size())//如果之前都对,且位置没有超出范围
{
if (p[j].right[pos] == tep) pos++;//继续判断下一个
else judge = false;
}
else
{
judge = false;//错误
}
}
cin >> tep;//去掉第二个的括号
if (judge)//如果答对该学生得分增加
{
sum += p[j].score;
}
else//否则该题的答错人数加1
{
p[j].person++;
}
}
cout << sum << endl;
}
最后找到错误最多的题目:
int max = 0;
for (int i = 0; i < M; i++)
{
max = max > p[i].person ? max : p[i].person;//找到错误人数最多的题
}
if (max==0)//如果所有题都没有人错,全队输出too simple
{
cout << "Too simple" << endl;
}
else
{
cout << max <<' ';//输入错的次数
int i;
//为了保证结尾无多余空格,需要先输出第一个不带空格的序号
for (i = 0; i < M; i++)//输入错的编号
{
if (p[i].person == max)
{
cout << p[i++].pn;
break;
}
}
for (; i < M; i++)
{
if (p[i].person == max)
{
cout << ' '<<p[i].pn;
}
}
}
代码:选择题
题目:C语言竞赛
思路:本题思路简单,由于人数小于10000,定义一个10000的数组,通过下标查询是最快的方法,对每一个ID,根据其排名,存储其获奖情况,1为一等奖,2为二等奖,3为三等奖,0为该人不存在,查询时直接根据下标进行查询,如果查询过后将获奖情况设为4,防止其二次查找。
//1059 C语言竞赛
#include
#include
using namespace std;
bool judge(int n)//判断是否是素数
{
if (n<=1)return false;//0,1负数都不是素数
if (n == 2)return true;//2是素数
if (n % 2 == 0)return false;//偶数不是素数
int s = int(sqrt(n)) + 1;
for (int i = 3; i < s; i += 2)
{
if (n%i == 0)return false;
}
return true;//前面都不能整除,是素数
}
int main()
{
int N;//学生人数
cin >> N;
int rank[10000] = {0};//用于存储每个人的排名
int temp;//存储每个人的ID
for (int i = 1; i <= N; i++)
{
cin >> temp;
if (i == 1)
{
rank[temp] = 1;
continue;//第一名神秘大奖记为1
}
if (judge(i))//如果排名是素数,记为2
{
rank[temp] = 2;
}
else//其他人记为2
{
rank[temp] = 3;
}
}
int K;//查询人数
cin >> K;
for (int i = 0; i < K; i++)
{
cin >> temp;
if (rank[temp] == 1)
{
printf("%04d: Mystery Award\n", temp);
rank[temp] = 4;//查询过将其表示为4
}
else if (rank[temp] == 2)
{
printf("%04d: Minion\n", temp);
rank[temp] = 4;//查询过将其表示为4
}
else if (rank[temp] == 3)
{
printf("%04d: Chocolate\n", temp);
rank[temp] = 4;//查询过将其表示为4
}
else if (rank[temp] == 4)
{
printf("%04d: Checked\n", temp);
}
else
{
printf("%04d: Are you kidding?\n", temp);
}
}
return 0;
}
代码:C语言竞赛
题目:爱丁顿数
思路:本题一开始觉得很难,一开始想使用在线处理,后来发现有点困难,之后发现使用一次排序以后,从最大数的开始计数就行,具体过程如下:
假设排序后为:2 3 6 6 7 7 8 8 9 10
从10往前开始数,E=0,判断10是否大于E+1(注意这里是E+1,一开始写成E很多测试用例没有通过)
如果大于,E++变为1,然后判断9是否大于2→8是否大于3→8是否大于4→7是否大于5→7是否大于6→6是否大于7,到这里发现6小于7,退出循环,输出E=6即可。
//1060 爱丁顿数
#include
#include
#include
using namespace std;
int main()
{
int N;//骑车天数
cin >> N;
vector<int> s(N);//存储路程
for (int i = 0; i < N; i++)
{
cin >> s[i];
}
sort(s.begin(), s.end());//对其进行排序
int E = 0;
for (int i = s.size() - 1; i >= 0; i--)
{
if (s[i] > E + 1)E++;
else break;
}
cout << E;
return 0;
}
代码:爱丁顿数
题目:判断题
思路:本题过于简单,不赘述,代码见链接
代码:判断题
题目:最简分数
思路:本题思路很简单,但是有几个坑,导致该题通过率不高:
1)注意是两个分数之间,所以不包括这两个分数
2)一开始使用double 类型存储两个分数的计算结果,然后乘以K找到i的范围,后来发现这样做第三个测试点一直不能通过(不知道什么原因,个人猜测是数据精度问题)后来用int存储n1,m1,n2,m2然后在循环内在计算范围,注意下限要加1,上限小于这个小数即可
for (int i = int(n1*K /m1) + 1;i < double(n2*K/(m2+0.0)); i++)
3)两个分数的大小需要判断,并非给的第一个分数小
4)使用辗转相除法计算最大公约数,保证判断速度
//1062 最简分数
#include
using namespace std;
int judge(int a, int b)//使用辗转相除法计算最大公约数
{
int c;
if (a>b)
{
c = a;
a = b;
b = c;
}
while (a != 0)//不为零则一直除
{
c = b%a;
b = a;
a = c;//余数给较小数
}
return b;//最后一次余数为零的除数即为最大公因子
}
int main()
{
int n1, m1,n2,m2, K;//记录分子分母与K
scanf("%d/%d %d/%d", &n1, &m1,&n2,&m2);
if (n1*m2 > n2*m1)
{
int tep = n1; n1 = n2; n2 = tep;
tep = m1; m1 = m2; m2 = tep;
}
cin >> K;//读取K
int flag = 0;
for (int i = int(n1*K/m1) + 1; i < double(n2*K/(m2+0.0)); i++)
{
if (flag==0&&judge(i,K)==1)
{
printf("%d/%d", i, K);
flag = 1;
continue;
}
if (judge(i, K) == 1)
{
printf(" %d/%d", i, K);
}
}
return 0;
}
代码:最简分数
题目:计算谱半径
思路:本题过于简单,不赘述,代码见链接
代码:计算谱半径
题目:朋友数
思路:本题思路比较简单,只要将朋友证明存到一个数组即可,由于题目要求小于10^4,因此只要定义一个长度为37的数组即可(9999的朋友数为36最大),对于每一个数,可以用字符串存储:
int trans(string s)//计算一个数的朋友证明
{
int n = 0;
for (unsigned int i = 0; i < s.length(); i++)
{
n += s[i] - '0';
}
return n;
}
关键的循环如下:
int N,m = 0;//记录整数个数与朋友证明的个数
int n[37] = {0};//存储朋友证明的数组,由于最大数9999的朋友证明为36,因此使用长度为37的数组即可
string temp;
cin >> N;
for (int i = 0; i < N; i++)
{
cin >> temp;
if (n[trans(temp)] == 0)//如果输入的朋友证明不存在,将其设为1;
{
n[trans(temp)] = 1;
m++;
}
}
代码:朋友数
题目:单身狗
思路:本题难度稍大,主要难点在于数组长度很大,以及判断的时候要判断是否有对象,且其对象是否参加了派对等情况,同时最后需要顺序输出单声狗。
因此定义结构体,储存每一个人的信息:
struct person
{
int id;
bool join = false;//暂定每一个人没有参加派对
int couple = -1;//一个人的伴侣id暂定为-1
bool judge = false;//判断是否输出
};
其中,id是ta的编号,join是记录其是否参加了派对,couple记录其伴侣的id(如果单身则设置为-1),judge是记录其最后是否输出
接下来是几个关键循环:
person *people=new person[MAX];
//为每一个人赋值ID
for (int i = 0; i < MAX; i++)
{
people[i].id = i;//为每一个人编号
}
//记录有伴侣的人其伴侣的ID
for (int i = 0; i < N; i++)
{
cin >> p1 >> p2;
people[p1].couple = p2;//将一人的对象指向另一人
people[p2].couple = p1;
}
//记录哪些人参加了派对,judge设置为true假定他们都要输出
for (int i = 0; i < M; i++)
{
cin >> p1;//记录参加派对人的id
people[p1].join = true;
people[p1].judge = true;
}
接下来是判断哪些是不需要关心的人士(非单身),判断条件是:该人参加了派对,且该人有对象,且该人的对象也来参加派对了
int count = 0;//记录非单身狗的个数
for (int i = 0; i < MAX; i++)
{
if (people[i].join&&people[i].couple != -1 && people[people[i].couple].join)//如果该人参加派对且有对象,同时其对象参加派对
{
count++;
people[i].judge = false;
}
}
cout << M-count << endl;//其余人为单身狗人数
最后将judge为true的人按照从小到大的顺序输出即可:
for (int i = 0; i < MAX; i++)
{
if (flag==0&&people[i].judge)//如果是第一个,且参加派对,且没有对象
{
printf("%05d",i) ;
flag = 1;
}
else if (flag&&people[i].judge)
{
printf(" %05d", i);
}
}
按照上面的思路,整个程序的时间复杂度是O(n),算是一次处理完毕,可以满足大数组的运行时间要求,不会超时。(由于此题一次通过了,不知道有什么坑点,个人猜测应该是运行时间超时是大坑吧)
注意使用大数组直接定义会溢出,因此使用指针数组定义
person *people=new person[MAX];
这样定义的数组长度可以很大
代码:单身狗
题目:图像过滤
思路:本题很简单,主要注意一点,读取数据的时候使用scanf而不是cin,这样最后一个测试用例就不会超时
代码:图像过滤
题目:试密码
思路:本题题目很简单,但是一开始被题干套路了,题干说正确密码是不包含空格、Tab、回车的非空字符串,但是没说输入的密码也是这样,有两个测试点的输入密码就包含空格,因此读取整行字符串不能直接使用cin,而要使用getline!
string password,temp;//储存正确密码与临时输入
vector<string> store;//储存输入
int allow;//存储最大尝试个数
cin >> password >> allow;
getchar();//读取第一行最后的回车
int count = 0;//记录尝试个数
while (1)
{
getline(cin, temp);//注意题目说正确密码不包含空格等,但是没说输入的密码不包含空格!因此这里要用getline读取整行
if (temp != "#")
store.push_back(temp);
else
break;
}
注意读取完最大尝试个数后要使用getchar取出这一行的回车符号,这样下一行就不会将回车认为是一次输入
判断密码正确的过程如下:
for (auto i = 0; i < allow&&i<store.size(); i++)
{
if (store[i] == password)
{
printf("Welcome in\n");
return 0;
}
else
{
printf("Wrong password: ");
cout << store[i] << endl;
}
}
if (allow <= store.size())cout << "Account locked" << endl;
代码:试密码
题目:万绿丛中一点红
思路:本题难度较大,一个难点是如何鉴别像素点颜色是否唯一,另一个难点是如何对每一个像素点与周围的点进行比较。
首先这个题需要定义一个全局的储存像素信息的二维数组,同时最好有一个可以记录像素点坐标与颜色的结构体变量:
struct node
{
int color;//储存颜色
int x, y;//储存坐标
};
int M, N;//记录像素
int TOL;//记录阈值
vector<vector<int>> v;//记录像素信息
同时因为要定义判断一个点是否与周围点色差足够大,需要定义一个判断函数,一开始是这样的代码:
if (judge(s[i][j], s[i+1][j]) && judge(s[i][j], s[i][j-1]) && judge(s[i][j], s[i+1][j-1])){...}
根据四个角点,四个边,分别写…后来发现代码实在是过于繁琐,而且不规范,借鉴了网上的方法写出了判断函数:
bool judge(int a,int b)
{
int p[8][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 },
{ -1, -1 }, { -1, 1 }, { 1, -1 }, { 1, 1 } };
//从一个坐标变到另一个,分别为上,下,左,右,左上,右上,左下,右下
for (int i = 0; i < 8; i++)
{
//记录相邻点的横纵坐标
int ac = a + p[i][0];
int bc = b + p[i][1];
if (ac >= 0 && ac < N && bc >= 0 && bc < M)//相邻坐标存在
{
if (abs(v[a][b] - v[ac][bc])>TOL)continue;//注意色差要超过才行
else return false;
}
}
return true;
}
这里巧妙的使用了一个8*2的数组与一个循环次数为8的for循环对每一个点的周围八个点进行判断,其中使用判断语句巧妙解决了角点与边点的情况。
接下来就是读入数据,由于v必须定义为全局变量,因此程序开始需要将v初始化,同时由于记录数据的时候同时要进行像素点出现次数的记录,传统的记录次数的方法在这里将不适用,因此需要使用关联性容器STL中的map,这在一开始需要#include< map>,使用map通过像素点颜色值为下标进行索引速度将非常快(搜索时间复杂度是logN)
int main()
{
cin >> M >> N >> TOL;
//存储像素点颜色信息;
v.resize(N);
map<int, int> store;//储存每一个点出现次数,first是颜色作为索引,second是次数是值
int temp;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
cin >> temp;
v[i].push_back(temp);//储存像素点信息
store[v[i][j]]++;//记录每一个颜色的出现的次数
}
}
记录完数据后,就需要根据题目要求,将独一无二的颜色找出来,并对其进行色差判断:
vector<node> result;//储存只出现一次且满足色差足够大的点
node tep;
//查找只出现一次且满足色差要求的点
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (store[v[i][j]] == 1 && judge(i, j))//只出现过一次的颜色,且判断色差足够大
{
tep.color = v[i][j];
tep.x = j + 1;
tep.y = i + 1;
result.push_back(tep);//将其存在结果数组中
}
}
}
最后输出结果:
if (result.size() == 0)//并没有只出现一次且色差足够大的点
{
cout << "Not Exist" << endl;
}
else if (result.size() == 1)//只有一个出现过一次且色差足够大的点
{
printf("(%d, %d): %d\n", tep.x, tep.y, tep.color);
}
else//有很多出现过一次且色差足够大的点
{
cout << "Not Unique" << endl;
}
return 0;
代码:万绿丛中一点红
题目:微博转发抽奖
思路:本题难度略大
主要是需要判断多次转发中奖的人,不能重复中奖。此定义一个数组储存转发的人,再定义一个集合储存中奖的人:
int M, N, S;//转发量,中奖间隔,第一个中奖人的序号
cin >> M >> N >> S; getchar();//读取第一行,并读取行末换行符
vector<string> v(M);//储存转发的人
set<string> win;//储存中奖的人
for (int i = 0; i < M; i++)
{
getline(cin, v[i]);//按行读取转发人数
}
同时,如果M
if (M < S)
{
cout << "Keep going..." << endl;
return 0;
}
接下来就要输出中奖的人,这里使用一个循环,首先判断这个位置的人是否中过奖,如果此前没有中奖,输出插入,如果中过奖,往后顺延
注意顺延的时候需要防止顺延的那次超出数组范围,重复判断是否中过奖,直到找到顺延那个没有中奖的人,输出,推出循环
for (int i = S - 1; i < M; i+=N)
{
if (win.find(v[i]) == win.end())//没有中过奖
{
cout << v[i] << endl;//输出
win.insert(v[i]);//插入
}
else//如果重复中奖,依次顺延
{
i++;//先顺延一次
while (i < M)//如果顺延的那次还在范围内,查找
{
if (win.find(v[i]) == win.end())//如果没有中过奖,则输出,推出循环
{
cout << v[i] << endl;
win.insert(v[i]);
break;
}
i++;
};
}
}
代码:微博转发抽奖
题目:结绳
思路:本题很简单(一点都不像25分题目…)
由于每一次操作后绳子长度是前面每一段加后一段除以二,因此要使得最后的绳子足够长,就要保证长度大的绳子要尽可能少对折,因此升序排列一下就可以,然后从小到大进行加和除以二即可
#include
#include
using namespace std;
int main()
{
int N;//绳子个数
cin >> N;
double *l = new double[N];
for (int i = 0; i < N; i++)
cin >> l[i];
sort(l, l + N);//排序
double length = l[0];//计算最后的绳子长,初始长度为最短的
for (int i = 1; i < N; i++)
{
length += l[i];
length /= 2;
}
cout << int(length) << endl;
return 0;
}
代码:结绳
题目:小赌怡情
思路:本题很简单,只要注意输出格式就好了,输出的Total前面是两个空格。
#include
using namespace std;
int main()
{
int T, K;//T是初始筹码,K是游戏次数
cin >> T >> K;
int n1, b, t, n2;//系统数字,猜大小,赌注,开的数字
for (int i = 0; i < K; i++)
{
cin >> n1 >> b >> t >> n2;
if (t > T)//赌注大于筹码
{
printf("Not enough tokens. Total = %d.\n", T);
continue;
}
if (n1 < n2 == b)//如果猜对
{
T += t;//增加筹码
printf("Win %d! Total = %d.\n", t, T);
}
else//否则
{
T -= t;//减少筹码
printf("Lose %d. Total = %d.\n", t, T);
}
if (T == 0)
{
printf("Game Over.\n");
return 0;
}
}
return 0;
}
代码:小赌怡情
题目:开学寄语
思路:本题比较简单,首先定义了一个结构体储存学生的信息:
struct stu
{
string name;//储存学生人数
vector<int> items;//储存该学生的物品
};
在主函数里,先将违规物品标号储存在set集合里,之后查找会比较方便:
int N, M;//储存学生人数与违规物品的个数
cin >> N >> M;
set<int> item;//储存违规物品的编号;
int tep;
for (int i = 0; i < M; i++)//储存物品编号
{
cin >> tep;
item.insert(tep);
}
之后储存学生信息,姓名与学生携带的物品:
vector<stu> v(N);//储存学生的信息
for (int i = 0; i < N; i++)
{
int L;//储存学生的物品个数
cin >> v[i].name >> L;
v[i].items.resize(L);
for (int j = 0; j < L; j++)
{
cin >> v[i].items[j];
}
}
最后对每一个学生进行违规品的检查,定义两个计数变量,储存违规学生的个数与所有的违规物品的个数(注意输出违规品的编号时候要不足4位要补足0,这是第三个测试点的坑):
int count1 = 0, count2 = 0;//记录违规学生人数,违规物品个数
for (int i = 0; i < N; i++)
{
int flag = 0;
for (int j = 0; j < v[i].items.size(); j++)
{
if (item.find(v[i].items[j]) != item.end()&&!flag)//如果找到并是第一个
{
cout << v[i].name;//输出名字
printf(": %04d", v[i].items[j]);
flag = 1; count1++;//违规人数加1
count2++;//违规物品数加1
continue;
}
if (item.find(v[i].items[j]) != item.end() && flag)//找到剩余的违规物品
{
printf(" %04d", v[i].items[j]);
count2++;//违规物品数加1
}
}
if(flag)cout << endl;//该学生违规输出一个换行
}
cout << count1 << ' ' << count2 << endl;
代码:开学寄语
题目:多选题常见计分法
思路:本题难度较大,难点在于要将错误项记录下来,并统计该选项的选错次数。一开始使用map来统计,将错误的“题目-选项”字符作为索引,对其进行计数,前四个测试用例都通过了(map会根据字符自动排序),发现死活都不能通过最后一个测试用例。
map m;
后来发现,对于题目个数超出10的情况,map的排序会与实际不同,例如10-a的位置大于1-a的位置,这样就不能使用map。想了很多方法,最后还是发现网上大神的方法最好,那就是定义一个M*5的二维数组,对错误的题目的错误的选项进行计数(果然闭门造车效率是极低的)
由于自己写了前面很多,最后综合了一下,完成了这道题目。思路如下:
首先定义两个结构体,一个用于储存题目信息,一个用于储存学生答题情况
struct que//定义储存题目答案的结构体
{
int id;//题目序号
double score;//题目满分
set<char> ans;
};
struct student//储存单个学生答题情况的结构体
{
double score = 0;//储存学生得分
vector<set<char>> ans;//学生选项
};
还要定义一个判断函数对学生每一个题的选择情况进行打分:
在这里a,b两个集合是正确选项集合与学生选项集合,打分结果为0表示该题答错,打分结果为1表示该题部分正确,打分结果为2表示该题答对
int judge(set<char> a, set<char> b)//判断两个选项是否正确,a是正确,b是学生答
{
if (a.size() < b.size())return 0;//如果学生选的个数超出,肯定错,记为0
for (set<char>::iterator it = b.begin(); it != b.end(); it++)
{
if (a.find(*it) == a.end()) return 0;//如果学生的选项不在正确里面
}
if (a.size() == b.size())return 2;//学生所有选项均在正确选项里且个数相同记为2
else return 1;//尽管都对,但是没有选全,记为1。
}
主函数分为两部分,第一部分是储存输入数据,将正确选项集合储存在v中,将学生答题情况储存在stu中
int N, M;//学生人数,多选题个数;
cin >> N >> M;
//储存题目信息
vector<que> v(M);
double score; int nc, rn; char c;//储存每个题的分数,总选项个数,正确选项个数,字符
for (int i = 0; i < M; i++)//储存答案信息
{
cin >> score >> nc >> rn;
v[i].id = i + 1;
v[i].score = score;
for (int j = 0; j < rn; j++)
{
cin >> c;
v[i].ans.insert(c);
}
}
//储存学生答题情况
vector<student> stu(N);
for (int i = 0; i < N; i++)
{
stu[i].ans.resize(M);
for (int j = 0; j < M; j++)
{
int temp; char cp;//读取学生选的个数与选项
while (getchar() != '(');//读取第一个空格
cin >> temp;
for (int k = 0; k < temp; k++)
{
cin >> cp;
stu[i].ans[j].insert(cp);//i学生的j题的第k个选项
}
while (getchar() != ')');//读取第二个空格
}
while (getchar() != '\n');//读取回车
}
然后是对每一个学生进行打分,同时记录错题的错误次数,根据之前所说,这里定义一个M*5的二维数组wr来储存错误选项的错误次数:
在一个双重循环中先对学生得分进行统计,如果该题没有完全答对,记录错误选项(注意这里不仅要判断错选,还要判断漏选)
vector<vector<int>> wr(M,vector<int>(5,0));
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
int flag = judge(v[j].ans, stu[i].ans[j]);//记录判断情况
//记录学生该题的得分
double plus;
if (flag == 0)plus = 0;
else if (flag == 1)plus = v[j].score / 2;
else plus = v[j].score;
stu[i].score += plus;
//如果此题学生没完全答对
if ( flag== 0||flag==1)
{
//记录错误答案
//记录错选
for (set<char>::iterator it = stu[i].ans[j].begin(); it != stu[i].ans[j].end(); it++)//先从学生的答案找出不是正确的
{
if (v[j].ans.find(*it) == v[j].ans.end())//如果不是正确答案
{
wr[j][(*it) - 'a']++;//第j题的该选项错误次数加1
}
}
//记录漏选
for (set<char>::iterator it = v[j].ans.begin(); it != v[j].ans.end(); it++)//在从正确的答案找出学生没答的
{
if (stu[i].ans[j].find(*it) == stu[i].ans[j].end())//如果不是正确答案
{
wr[j][(*it) - 'a']++;
}
}
}
}
}
最后输出结果:
首先输出每个同学的得分,然后再wr数组中查找错误最多的选项的错误次数max,最后根据max输出错误选项
//首先输出所有人的分数
for (int i = 0; i < N; i++)
{
printf("%.1f\n", stu[i].score);
}
//查找错误最多的选项的个数
int max = 0;
for (int i = 0; i < M; i++)
{
for (int j = 0; j < 5; j++)
max = max>wr[i][j] ? max : wr[i][j];
}
if (max == 0)printf("Too simple\n");
else
{
for (int i = 0; i < M; i++)
{
for (int j = 0; j < 5; j++)
{
if (max == wr[i][j])
{
printf("%d %d-%c\n", max, i+1, 'a' + j);
}
}
}
}
代码:多选题常见计分法
题目:宇宙无敌加法器
思路:本题难度不大,但是一开始不知道什么情况,一直两个测试点不能通过,参考网上大神们的思路,使用了字符串来进行处理,虽然通过了,但是还是没有找到之前的方法为什么出错,总觉得这个题的出题思路有很大问题。
本题使用string处理,将所有数组存在string中,最重要一点是将所有的字符串补到相同位数,在这里涉及到一个string的构造方法(如果不是看别人,还真的不知道可以这样构造)
string s(int length, char c);
其中构造的时候前面参数是字符串长度,后面是填充的字符
string rule, p1, p2;
cin >> rule >> p1 >> p2;
//将相加的字符串补充至与计算规则相同的位数
int l = rule.length();
string res(l, '0');
string p1s(l - p1.length(), '0');
string p2s(l - p2.length(), '0');
p1 = p1s + p1;
p2 = p2s + p2;
然后就是计算加分,注意最后如果还有进位,需要在前面再加上进位
//临时记录结果与进位
int tep, plus = 0;
for (int i = l - 1; i >= 0; i--)
{
int mod = rule[i]== '0' ? 10 : rule[i] - '0';//记录进制规则
//计算结果与进位
tep = ((p1[i] - '0') + (p2[i] - '0') + plus) % mod;
plus = ((p1[i] - '0') + (p2[i] - '0') + plus) / mod;
res[i] = tep + '0';
}
if (plus != 0)//如果所有位算完还有进位,在前面加上进位
{
char c = '0' + plus;
res = c + res;
}
最后输出结果,注意要避免前面的零,如果最后一个数字都没输出,输出结果为0
int flag = 0;
for (int i = 0; i < res.length(); i++)
{
if (res[i] != '0'||flag)
{
flag = 1;
cout << res[i];
}
}
if (!flag)cout << 0;
话说自己一开始使用deque跟vector补充0位的方法好蠢啊
代码:宇宙无敌加法器
题目:链表元素分类
思路:本题跟之前1025题类似,但是难度低很多,还是定义node结构体储存节点信息,然后根据节点的值,按照顺序分三类存在vector数组中,注意如果节点的下一个节点不存在,就不往数组中存了。
首先还是建立大数组,将节点存在以位置为下标索引的位置:
int first, N, K;//第一个节点的地址,节点个数,区间K
cin >> first >> N >> K;
int a, d, n;//临时存储位置,数据,下一节点位置
vector<node> v(100000);
for (int i = 0; i < N; i++)
{
cin >> a >> d >> n;
v[a].address = a;
v[a].data = d;
v[a].next = n;
}
然后根据是否小于0,是否在0-K之间,是否大于K三类将节点存在三个数组中,然后合并。(注意如果下一个节点不存在,无无需继续存储节点了,倒数第二个测试用例的坑就在这里)
vector<node> p1,p2,p3;//按照链表的顺序,存储小于零的,[0,K]的,大于K的
for (int i = 0; i < N; i++)
{
if (v[first].data < 0)
p1.push_back(v[first]);
else if (v[first].data>K)
p3.push_back(v[first]);
else
p2.push_back(v[first]);
first = v[first].next;
if (first == -1)break;//注意这里,如果节点下一个位置是-1,证明链表结束,倒数第二个测试点就是这个坑
}
//将三类节点合并
vector<node> p=p1;
//insert函数的第一个参数时插入位置,第二个参数是被插入的数组的起始位置,第三个参数是被插入的数组的终止位置
p.insert(p.end(),p2.begin(), p2.end());
p.insert(p.end(), p3.begin(), p3.end());
最后输出结果即可
//输出结果
for (int i = 0; i < p.size()-1; i++)
{
printf("%05d %d %05d\n", p[i].address, p[i].data, p[i + 1].address);
}
printf("%05d %d -1\n", p[p.size()-1].address,p[p.size()-1].data);
奇怪的是,我一开始想再定一个根据data为索引,address为值的map容器,存储节点的位置,但是最后一个测试案例一直不能通过,很是诧异,改为用vector存储就没问题,估计最后一个测试用例中,有存在data相同的数据,但是位置信息不同,这样就无法存入map中。
代码:链表元素分类
题目:Wifi密码
思路:本题很简单,不必赘述,代码如下:
#include
#include
#include
using namespace std;
int main()
{
int N;//题目个数
cin >> N; getchar();
vector<string> s(N);
for (int i = 0; i < N; i++)
{
getline(cin, s[i]);
}
vector<int> pass;//记录wifi密码
//查询密码
for (int i = 0; i < N; i++)
{
int p = 2;//查询题目答案
for (int j = 0; j < 4; j++)
{
if (s[i][p] == 'T')
{
pass.push_back(s[i][p-2]-'A'+1);
break;
}
else
{
p += 4;
}
}
}
//输出wifi密码
for (int i = 0; i < pass.size(); i++)
cout << pass[i];
cout << endl;
return 0;
}
代码:Wifi密码
题目:互评成绩计算
思路:本题比较简单,注意需要去掉最高最低分,同时要保证分数的合法与结果的四舍五入即可,代码如下:
#include
#include
using namespace std;
int M;//满分
bool judge(int n)//判断分数是否合法
{
if (n >= 0 && n <= M)return true;
else return false;
}
int main()
{
int N;//分组数与满分
cin >> N >> M;
vector<int> score(N);
for (int i = 0; i < N; i++)
{
cin >> score[0];
double G1 = score[0],G2=0,G=0;//老师的分数,学生的分数,最终的分数
int number = 0;
double sum = 0,max=0,min=50;//记录改组的最高分与最低分与总分
//统计学生评分
for (int j = 1; j < N; j++)
{
cin >> score[j];
if (judge(score[j]))//首先评分合法
{
//记录最高最低分
max = max > score[j] ? max : score[j];
min = min < score[j] ? min : score[j];
number++;
sum += score[j];
}
}
G2 = (sum - max - min) / (number - 2);
G = (G1 + G2) / 2;
cout << int(G + 0.5) << endl;//此处四舍五入
}
return 0;
}
代码:互评成绩计算
题目:字符串压缩与解压
思路:本题比较复杂,但是仔细想一下还是很好做的,分成两个来做:
压缩:
string compress(string s)//压缩
{
string com = "",tep;
stringstream ss;
int count = 1;
//对比前一个字符与后一个字符是否相同,相同计数加1,不相同的话证明变了一个字符,这样前面所有字符的个数变为一个字符串,与前面字符组合为:数字+字符的形式。要注意如果计数为1,证明这是一个单独的字符,前面不需要1
for (int i = 1; i < s.length(); i++)
{
if (s[i] == s[i - 1])
count++;
else
{
if (count == 1)
{
com += s[i-1];
continue;
}
//不是1的时候,使用stringstream将数字转化为字符
ss << count;
ss >> tep;
com += tep;//加数字
com += s[i-1];
ss.clear();
count = 1;
}
}
//还要判断最后的情况
if (count == 1)
{
com += s[s.length() - 1];
return com;
}
ss << count;
ss >> tep;
com += tep;
com += s[s.length() - 1];
return com;
}
解压:
string decompress(string s)
{
string dec = "";
stringstream ss;
int f=0;//记录数字的首与位数
if (!(s[0] >= '0'&&s[0] <= '9'))//如果首位不是数字
{
dec += s[0];
}
//从第二位开始判断
for (int i = 1; i < s.length(); i++)
{
if (!(s[i] >= '0'&&s[i] <= '9'))//找到直到不是数字的时候
{
if (!(s[i - 1] >= '0'&&s[i - 1] <= '9'))//如果前一位不是数字,证明这个是单独的字母
{
dec += s[i];
f = i + 1;//数字的首位暂定为下一位
continue;
}
else//如果前一位是数字,证明是一个重复的字母
{
string plus=s.substr(f, i-f);//将数字存起来,并将字符串转化为数字,此函数是返回s的子串,从f开始,的i-f个数的字符
ss << plus;
int p;
ss >> p;
ss.clear();
string plu(p, s[i]);//构造长度为p字符为s[i]的字符
dec += plu;
//数字的首位暂定为下一位
f = i + 1;
}
}
}
return dec;
}
注意在这里使用了substr(int a,int b)函数来构造一个从主串a位置,长度为b的字符串,来储存数字的字符串形式,然后使用stringstream流,将该字符串转化为数字
主函数:
int main()
{
char c;
string s;
cin >> c; getchar();
getline(cin, s);
if (c == 'C')
{
cout << compress(s);
}
else
{
cout << decompress(s);
}
return 0;
}
代码:字符串压缩与解压
题目:延迟的回文数
思路:本题比较简单,但是!有三个测试点竟然都是直接输入回文数,然后直接输出是回文数,没有计算过程……有点坑。
思路如下:将字符串逆序相加即可,主要就是相加字符串的函数:
string pla(string a,string b)//计算自身与逆序和
{
string pl = "";
int plus = 0;
int p;
for (int i = a.length()-1; i >=0; i--)
{
p = ((a[i] - '0') + (b[i] - '0') + plus) % 10;//相加
plus = ((a[i] - '0') + (b[i] - '0') + plus) / 10;//进位
char tep = '0' + p;
pl = tep + pl;
}
if (plus != 0)
{
char tep = '0' + plus;
pl = tep + pl;
}
return pl;
}
源代码如下:
代码:延迟的回文数
题目:MOOC期终成绩
思路:本题思路较为简单,使用map这个关联性容器即可,key为学号,value为一个结构体:
注意要对这个结构体内的分数进行初始化,设为-1,最后一个测试用例中,有的人是参加了考试但是分数为0,与没有参加考试需要记为-1要区分开。
struct score
{
int GP=-1,GM=-1,GF=-1,G;//编程作业得分,期中得分,期末得分,总评分;
};
还是将所有人的成绩存在map
cin >> P >> M >> N;
int number[3] = { P, M, N };
map<string, score> m;
string tep; int temp;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < number[i]; j++)
{
cin >> tep >> temp;
switch (i)
{
case 0:
m[tep].GP = temp;
break;
case 1:
m[tep].GM = temp;
break;
case 2:
m[tep].GF = temp;
break;
default:
break;
}
}
}
然后计算每个人的总成绩,,注意要四舍五入:
for (auto it = m.begin(); it != m.end(); it++)
{
double GP = (*it).second.GP, GM = (*it).second.GM, GF = (*it).second.GF;
if (GP >= 200)
{
if (GM == -1)GM = 0;
if (GF == -1)GF = 0;
(*it).second.G = GM > GF ? int((GM*0.4 + GF * 0.6+0.5)) : int(GF);
}
}
还是将关联性容器中将元素取出存在vector中,根据compare规则,进行排序
vector<PAIR> v;
for (auto it = m.begin(); it != m.end(); it++)
{
if ((*it).second.G >= 60)
v.push_back(*it);
}
sort(v.begin(), v.end(), compare);
compare排序规则:
typedef pair PAIR;
bool compare(PAIR a, PAIR b)
{
if (a.second.G > b.second.G)return true;
else if (a.second.G < b.second.G)return false;
else
{
if (a.first < b.first) return true;
else return false;
}
}
最后输出结果即可
for (int i = 0; i < v.size(); i++)
{
cout << v[i].first;
for (int j = 0; j < 4; j++)
{
switch (j)
{
case 0:
cout << ' ' << v[i].second.GP;
break;
case 1:
cout << ' ' << v[i].second.GM;
break;
case 2:
cout << ' ' << v[i].second.GF;
break;
case 3:
cout << ' ' << v[i].second.G;
break;
default:
break;
}
}
cout << endl;
}
代码:MOOC期终成绩
题目:检查密码
思路:本题比较简单,首先定义一个判断字符属于什么类型的函数:
int judge(char c)//判断字符是否合法
{
if (c >= '0'&&c <= '9')return 1;
else if ((c >= 'A'&&c <= 'Z') || (c >= 'a'&&c <= 'z')) return 2;
else if (c == '.')return 3;
else return 0;
}
然后定义判断密码是否合法的函数:
int res(string s)//判断密码属于什么类型
{
int flag1 = 0, flag2 = 0;//记录数字与字母是否出现
if (s.length() < 6)return 1;//长度太短
for (int i = 0; i < s.length(); i++)
{
if (judge(s[i]) == 1)
{
flag1 = 1;
continue;
}
else if (judge(s[i]) == 2)
{
flag2 = 1;
continue;
}
else if (judge(s[i]) == 3)
{
continue;
}
else
return 2;//存在不合法字符
}
if (!flag1&&flag2)return 3;//只有字母没有数字;
if (flag1&&!flag2)return 4;//只有数字没有字母;
if (!flag1&&!flag2) return 5;//只有.
return 0;//合法的密码;
}
主函数根据判断结果输出应该显示的文字:
int main()
{
int N;//记录N个字符串
cin >> N; getchar();
string temp;//存储临时密码
int f;//记录判断结果
for (int i = 0; i < N; i++)
{
getline(cin, temp);
f = res(temp);
if (!f)
cout << "Your password is wan mei." << endl;
else if (f == 1)
cout << "Your password is tai duan le." << endl;
else if (f == 2)
cout << "Your password is tai luan le." << endl;
else if (f == 3)
cout << "Your password needs shu zi." << endl;
else if (f == 4)
cout << "Your password needs zi mu." << endl;
else
continue;
}
return 0;
}
代码:检查密码
题目:射击比赛
思路:本题很简单,使用map将成绩与id一一即可,最后输出map中的首位与末尾元素,代码如下:
#include
#include
#include
using namespace std;
double dis(double x, double y)
{
return sqrt(x*x + y*y);
}
int main()
{
int N;//记录运动员个数
cin >> N;
int id; double x, y;
map<double,int> score;//记录ID与其匹配的成绩
//将成绩储存在map中一一对应
for (int i = 0; i < N; i++)
{
cin >> id >> x >> y;
score.insert(pair<double, int>(dis(x, y),id));
}
//由于score自动按照first升序排列,直接输出第一个与最后一个元素的id即可
map<double,int>:: iterator it=score.end();
it--;
printf("%04d %04d\n", score.begin()->second, it->second);//注意距离最远的是菜鸟
return 0;
}
代码:射击比赛
题目:是否存在相等的差
思路:本题比较简单,使用map存储不同差值出现的次数即可:
#include
#include
#include
using namespace std;
int main()
{
int N;//N张牌
cin >> N;
int n;//记录每一张牌背后的数字
map<int, int> m;//记录差值出现的次数,前者为差值,后者为次数
//存储不同差值的数出现的次数
for (int i = 1; i <= N; i++)
{
cin >> n;
m[abs(n - i)]++;//该差值的个数加1
}
//从大到小输出有多少不同值
map<int, int>::iterator it=m.end();
it--;
for (int i = 0; i < m.size(); i++)
{
if (it->second == 1){ it--; continue; }//如果该差值只出现了一次,不输出
cout << it->first << ' ' << it->second << endl;
it--;
}
return 0;
}
由于map自动按照差值的升序进行排列,只要从最后一位开始判断输出即可
代码:是否存在相等的差
题目:外观数列
思路:本题比较简单,定义一个计算数列下一个外观数的函数即可:
//定义计算一个数列的下一个外观数列的函数
string Next(string s)
{
string next;
next = "";
char c;
int count = 1;//记录字符出现的次数
//从第二位开始判断,如果与前面字符相同,则增加count,若不同,则在下一个外观数列中加入数数的结果
for (int i = 1; i < s.length(); i++)
{
if (s[i] == s[i - 1])
count++;
else
{
next += s[i - 1];
c = '0' + count;
next += c;
count = 1;
}
}
//记录最后的情况
next += s[s.length() - 1];
c = '0' + count;
next += c;
return next;
}
注意这里函数我一开始定义的名字为next,编译一直不能通过,后来查询了很久了,找到原因是自定义的函数名不能与系统的文件的函数名相同,改成Next就通过了
主函数:
int main()
{
string s;
int N;
cin >> s >> N;
for (int i = 1; i < N; i++)
{
s = Next(s);
}
cout << s;
return 0;
}
代码:外观数列
题目:PAT单位排行
思路:本题略有难度,主要是在于排序比较复杂,思路如下:
首先定义结构体与PAIR变量:
struct school
{
//分数与学校人数
double score;
int pn;
};
typedef pair PAIR;//定义数据类型PAIR
然后定义map,是学校的小写名称为引索的集合类型,将该储存的数据储存在map中:
int N;//考生人数;
cin >> N;
string s, sn;//准考证号与学校名称
int grade;//成绩;
map<string, school> m;//储存学校信息stl
for (int i = 0; i < N; i++)
{
cin >> s >> grade >> sn;
for (int j = 0; j < sn.length(); j++)
{
if (sn[j] >= 'A'&&sn[j] <= 'Z')sn[j] = sn[j] - 'A' + 'a';
}
if (s[0] == 'B')
m[sn].score += grade / 1.5;
else if (s[0] == 'A')
m[sn].score += grade;
else
m[sn].score += grade*1.5;
m[sn].pn++;
}
由于map无法同时根据key与value对元素进行排序,因此将map中的元素取出,存在序列型容器,然后使用sort函数进行排序:
vector<PAIR> v(m.begin(), m.end());
sort(v.begin(), v.end(), compare);
其中排序方法使用了经常使用的sort函数,第三个变量是bool类型函数:
bool compare(const PAIR &a, const PAIR &b)
{
//首先根据分数进行排序
if (int(a.second.score) >int( b.second.score))return true;
else if (int(a.second.score) < int(b.second.score))return false;
else
{
//其次根据人数进行排序
if (a.second.pn < b.second.pn)return true;
else if (a.second.pn>b.second.pn)return false;
else
{
//最后根据学校名称进行排序
if (a.first < b.first)return true;
else return false;
}
}
}
最后按照要求输出结果即可
cout << v.size() << endl;
//先输出第一名
int rank = 1;
cout << rank << ' ' << v[0].first << ' ' << int(v[0].second.score) << ' ' << v[0].second.pn << endl;
for (int i = 1; i < v.size(); i++)
{
//如果后一名的分数与前一名不同,该名词则为序列号+1的名次
if (int(v[i].second.score) != int(v[i - 1].second.score))
rank = i + 1;
cout << rank << ' ' << v[i].first << ' ' << int(v[i].second.score) << ' ' << v[i].second.pn << endl;
}
代码:PAT单位排行
题目:就不告诉你
思路:本题很简单,使用stringstream完成数字与字符串的转化即可,注意如果计算的数字最后几位是0,倒序首位的0不应该输出。
#include
#include
#include
using namespace std;
int main()
{
int a, b, mul;
cin >> a >> b;
mul = a*b;
//将数字转化为字符串
stringstream ss;
string s;
ss << mul;
ss >> s;
//注意如果计算结果最后几位是0,那么倒序后不应输入开头的0
int flag = 0;
for (int i = s.length() - 1; i >= 0; i--)
{
if (!flag && s[i] == '0')
{
continue;
}
else
{
flag = 1;
}
cout << s[i];
}
return 0;
}
代码:就不告诉你
题目:有多少不同的值
思路:本题很简单,使用set存储计算出现过的数字即可,在此不再赘述,代码如下:
#include
#include
using namespace std;
int cal(int n)
{
return n / 2 + n / 3 + n / 5;
}
int main()
{
int N;//记录N的值
cin >> N;
set<int> s;//记录存在过的计算值
for (int i = 1; i <= N; i++)
{
s.insert(cal(i));
}
cout << s.size();
return 0;
}
代码:有多少不同的值
题目:三人行
思路:本题比较简单,计算出三人的能力值即可,但是需要注意的是,丙的能力不一定是整数,因此定义输出函数的时候参数应该是double类型的才行,不然会有一个测试用例无法通过
#include
#include
using namespace std;
void print(double a, double b)//注意由于丙的能力不一定是整数,这里需要使用double类型进行判断
{
if (a > b)
cout << "Cong";
else if (a == b)
cout << "Ping";
else
cout << "Gai";
}
int main()
{
int M, X, Y;//我的能力,倍数关系X,Y
cin >> M >> X >> Y;
int n1 = 9, n2 = 9;//设甲能力为10*n1+n2,乙则为10*n2+n1,丙为9*abs(n1-n2)/X,同时丙为(10*n2+n1)/Y
double c1, c2;//丙的能力,两种计算方法
int flag = 0;
while (n1 || n2)
{
c1 = 9 * abs(n1 - n2) / (X + 0.0); c2 = (10 * n2 + n1) / (Y + 0.0);
if (c1 == c2)break;
else
{
n2--;
}
if (n2 == -1)
{
n1--;
n2 = 9;
}
if (n1 == 0 && n2 == 0)//所有二位数都找遍了
{
flag = 1;
}
}
if (flag)
cout << "No Solution" << endl;
else
{
int a = 10 * n1 + n2, b = 10 * n2 + n1;
cout << a << ' ';
print(a, M); cout << ' ';
print(b, M); cout << ' ';
print(c1, M); cout << endl;
}
return 0;
}
代码:三人行
题目:狼人杀-简单版
思路:本题难度比较大,主要是算法思路不是很好想,一开始想找规律,但是发现并没有什么好的方法,参考网上大神们的思路,才发现只能采用暴力穷举的方法来判断。
穷举的关键点在于读取题目的关键点:
因此知道这三个关键点就可以方便穷举了,依次设定i,j为狼人,然后依据这个假设,判断所有人的话是不是说谎,根据说谎的人中必有一个狼人,必有一个好人就可以判断出来。
题目中第二种有多解的情况,其实就是i,j从小到大开始试验,只要找到第一个解就输出即可。
具体如下:
首先定义结构体储存一个人的情况,与判断一个人是否说谎的判断函数。
struct people
{
char c;//记录此人指认的人是好人还是坏人;
int p;//记录此人指认的人的编号;
bool judge = true;//记录此人是好人还是坏人,默认好人
};
bool compare(people a, people b)//判断a指认b这句话是否是谎话
{
//如果a指认b是好人,b确实是好人
if (a.c == '+'&&b.judge)return true;
//如果a指认b是坏人,b确实是坏人
if (a.c == '-'&&!b.judge)return true;
return false;//此人说谎
}
主函数首先记录每个人的指认情况。
int N;//总共N个玩家
cin >> N; getchar();
vector<people> v(N + 1);//存储玩家信息;
for (int i = 1; i <= N; i++)
{
v[i].c = getchar();
cin >> v[i].p; getchar();
}
三种循环找出狼人:
需要注意的是,在进入第三重循环之前,先判断假设的两个狼人的话是否是一真一假,如果不是证明这个假设是不对的,不进入第三重循环。
另外遍历说谎的人的个数的时候,一旦找到超出两个说谎的人数,证明假设也是错误的,也推出循环,只有在说谎人数恰好等于2的情况才说明是正确解
for (int i = 1; i <= N - 1; i++)
{
v[i].judge = false;//假设i为狼
for (int j = i + 1; j <= N; j++)
{
v[j].judge = false;//假设j为狼人
int flag1 = 0, flag2 = 0;
//首先判断是不是狼中只有一个人说谎
if (compare(v[i], v[v[i].p]))flag1 = 1;//说实话
if (compare(v[j], v[v[j].p]))flag2 = 1;//说实话
if ((flag1 == 1 && flag2 == 1) || (flag1 == 0 && flag2 == 0))
{
//如果两个狼人都是实话或者都是假话不符合题目;
v[j].judge = true;
continue;
}
//进行到这一步已经保证了两个狼人有一人说真话,一人说假话
//后进行遍历寻找说谎的人的个数,必须保证有且仅有两人说谎,并且一人是狼,一人是人
int cnt = 0;
for (int k = 1; k <= N; k++)
{
//记录说谎的人的个数
if (!compare(v[k], v[v[k].p]))cnt++;
if (cnt == 3)break;
}
if (cnt == 2)//如果恰好两人说谎,满足题意输出
{
cout << i << ' ' << j << endl;
return 0;
}
v[j].judge = true;
}
v[i].judge = true;//将i重新设为好人
}
//前面都没有输出结果过
cout << "No Solution" << endl;
代码:狼人杀-简单版
题目:危险品装箱
思路:本题比较复杂,主要是查找方法一定要快速,本题这里采用暴力查找,虽然侥幸全部AC但是之后应该有更加好的思路,先暂时记下
首先,需要将不相容的物品存储下来:
int N, M;//成对不相容的物品的对数,集装箱个数
cin >> N >> M;
map<int, set<int>> m;//储存m物品的不相容物品;
int a, b;//临时记录不相容物品编号
for (int i = 0; i < N; i++)
{
cin >> a >> b;
m[a].insert(b);
m[b].insert(a);
}
这里采用的map的second是一个集合类型的容器,查找方便,这也是之后能通过的主要原因,存储的时候要同时记录两个物品相互排斥的两个集合
记录好就可以对每一个集装箱进行查找,在for循环里,首先将所有物品的编号存储起来,使用scanf也能加快程序运行速度,储存后对每一个元素与后面所有元素进行判断。
首先如果该元素的不相容物品集合个数为0,则不需要判断
(注意这句话大大加快程序运算速度,第三个测试点一开始超时就是没有这一步)
如果有不相容物品,后面物品在该物品的不相容物品集合中进行查找,如果找到,这个集装箱就是不合法的。
for (int i = 0; i < M; i++)
{
int n;//记录每一个集装箱的物品数;
cin >> n;
vector<int> v(n);//存储每个物品的编号
for (int j = 0; j < n; j++)
{
scanf("%d", &v[j]);
}
//判断有没有不相容物品
bool judge = true;
for (int j = 0; j < n - 1; j++)
{
//如果该物品没有不相容的物品
if (m[v[j]].size() == 0)
{
//直接跳过本次判断
continue;
}
//如果有不相容的物品,在后面查找
for (int k = j + 1; k < n; k++)
{
//后面物品在该物品的不相容物品集合中进行查找,如果找到,这个集装箱就是不合法的
if (find(m[v[j]].begin(), m[v[j]].end(), v[k]) != m[v[j]].end())//如果找到了不相容的
{
judge = false;
break;
}
}
if (!judge)break;
}
if (judge)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
使用find函数需要include< algorithm >这个头文件,在此不再赘述。
代码:危险品装箱
题目:N-自守数
思路:本题很简单,构建函数计算N的自守数即可,根据n的位数规定求余数的数
int own(int n)
{
int mod;
if (n < 10)
mod = 10;
else if (n < 100)
mod = 100;
else
mod = 1000;
for (int i = 1; i < 10; i++)
{
if (n*n*i%mod == n)
return i;
}
return 0;//没有自守数返回0
}
主函数:
int main()
{
int M;//M个待检验数字
cin >> M;
int tep,res;//储存待检测的数字与自守数的计算结果
for (int i = 0; i < M; i++)
{
cin >> tep;
res = own(tep);
if (res != 0)
cout << res << ' ' << res*tep*tep << endl;
else
cout << "No" << endl;
}
return 0;
}
代码:N-自守数
题目:最好吃的月饼
思路:本题过于简单,不在此赘述,代码如下:
#include
#include
using namespace std;
int main()
{
int N, M;//N种月饼,M个城市
cin >> N >> M;
int tep;
vector<int> v(N);//记录N种月饼的销量
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
{
cin >> tep;
v[j] += tep;
}
}
//查找最大的销量
int max = 0;
for (int i = 0; i < N; i++)
{
max = max > v[i] ? max : v[i];
}
cout << max << endl;
//如果是最大销量,输出月饼编号
int flag = 0;
for (int i = 0; i < N; i++)
{
if (v[i] == max&&!flag)
{
cout << i + 1;
flag = 1;
}
else if (v[i] == max&&flag)
{
cout << ' ' << i + 1;
}
}
cout << endl;
return 0;
}
代码:最好吃的月饼
题目:字符串A+B
思路:本题很简单,将两个字符串的字符放在vector中,如果这个字符出现过,不放入,最后输出vector内元素即可(此方法比较笨,但是也全AC了,后来看了网上大神们的方法,发现用int[128]的数组记录是否输出过运算速度可以快一倍,果然闭门造车是不可取的)
我的思路如下,其中使用了算法里的find函数
#include
#include
#include
#include
using namespace std;
int main()
{
//记录两个字符串
string a, b,s;
getline(cin, a);
getline(cin, b);
s = a + b;
vector<char> v;
for (int i = 0; i <s.length(); i++)
{
//如果v中没有s[i],往里面加入
if (find(v.begin(), v.end(), s[i]) == v.end())
v.push_back(s[i]);
}
for (int i = 0; i < v.size(); i++)
{
cout << v[i];
}
cout << endl;
return 0;
}
代码:字符串A+B
题目:谷歌的招聘
思路:看到本题的时候很亲切,去年第一次参加PAT考试,不知天高地厚地报名了甲级,第一题就是这个题(话说甲级主要是英语看不懂,prime愣是不知道什么意思)当时知识储备很低,做这个题用了很长时间,现在学习了很多,乙级也快刷完了,再看这个题真的是送分题啊,思路很简单,只要从首位取K位字符,然后将其转化为数字进行判断是否是素数即可:
#include
#include
#include
#include
using namespace std;
//判断一个数是否是质数
bool judge(long long n)
{
if (n < 2)return false;//如果小于2的整数,不可能是素数
if (n == 2)return true;//2是素数
if (n % 2 == 0)return false;//其他2的倍数不是素数
for (int i = 3; i <= sqrt(n); i++)
{
if (n%i == 0)return false;
}
return true;
}
int main()
{
int L, K;//L长度的数,K位素数
cin >> L >> K; getchar();
string s;
getline(cin, s);
//定义stringstream流进行字符与数字的转化
stringstream ss;
long long n;
for (int i = 0; i <= L - K; i++)
{
//取出从i开始K长度的字符串
string tep = s.substr(i, K);
ss << tep; ss >> n; ss.clear();
if (judge(n))
{
cout << tep << endl;
return 0;
}
}
cout << "404" << endl;
return 0;
}
使用了stringstream流与字符串的substr函数
代码:谷歌的招聘
题目:解码PAT准考证
思路:本题可以说是乙级最难的几道题之一了,主要是流程太多,其次排序如果不用STL是不可能全部AC的(上次报名考试真是年轻啊)
先说一下后两个测试点运行超时的原因,最后一个测试点,如果使用cout输出肯定会超时,不能通过,必须改为使用printf,倒数第二个测试点,必须既要使用printf还要改为其使用unordered_map这个无序容器,不然使用map还是会超时。
知道了这个就很容易了,首先定义结构体:
struct student
{
string ID;//记录准考证
string rank;//记录等级
string room;//记录考场
string date;//记录日期
string id;//考生编号
int score;//考生成绩
};
typedef pair PAIR;
为case1与case3的排序方法进行设置:
排序变量都是
bool compare1(const PAIR &a, const PAIR &b)//PAIR的first为准考证,second为分数
{
return a.second != b.second ? a.second > b.second:a.first < b.first;
//按照分数递减顺序,对于分数并列的考生,按其准考证号的字典序递增输出
}
bool compare2(const PAIR &a, const PAIR &b)//PAIR的fisrt为考场编号,second为人数
{
return a.second != b.second ? a.second > b.second : a.first < b.first;
//按人数非递增顺序,若人数并列则按考场编号递增顺序
}
将储存学生信息的数组设为全局变量
int N, M;//记录考生人数与要统计的个数
vector v;//储存学生信息
针对三种情况设置三个函数:
函数传入的参数是考试等级,为T、A、B三种
void case1(string s)
{
unordered_map<string, int> m;//这里要不要使用无序容器都可以,使用无序会快一点
for (int i = 0; i < N; i++)
{
if (v[i].rank == s)
m[v[i].ID] = v[i].score;
}
if (m.empty())
{
cout << "NA" << endl;
return;
}
vector<PAIR> vv;
for (auto it = m.begin(); it != m.end(); it++)
{
vv.push_back(*it);
}
sort(vv.begin(), vv.end(), compare1);//排序准则是上面的函数
for (int i = 0; i < vv.size(); i++)
{
printf("%s %d\n", vv[i].first.c_str(), vv[i].second);//注意这里必须用printf而不是cout,不然后两个测试点不能通过
//cout << vv[i].first << ' ' << vv[i].second << endl;
}
return;
}
传入的参数是考场编号
void case2(string s)
{
int peo = 0, sum = 0;
int flag = 0;//用flag来记录有没有查询到
for (int i = 0; i < N; i++)
{
if (v[i].room == s)
{
flag = 1;
peo++;
sum += v[i].score;
}
}
if (!flag)
{
cout << "NA" << endl;
return;
}
cout << peo << ' ' << sum << endl;
return;
}
传入参数是考试日期,注意这里要使用unordered_map来储存不同考场编号对应的人数,不然使用map的时候,插入时也会排序,运行会超时
void case3(string s)
{
unordered_map<string, int> m;//考场编号对应人数
for (int i = 0; i < N; i++)
{
if (v[i].date == s)
m[v[i].room]++;
}
if (m.empty())
{
cout << "NA" << endl;
return;
}
vector<PAIR> vv;
for (auto it = m.begin(); it != m.end(); it++)
{
vv.push_back(*it);
}
sort(vv.begin(), vv.end(), compare2);
for (int i = 0; i < vv.size(); i++)
{
printf("%s %d\n", vv[i].first.c_str(), vv[i].second);
//cout << vv[i].first << ' ' << vv[i].second << endl;
}
return;
}
最后是主函数:
int main()
{
cin >> N >> M;
v.resize(N);//对全局数组进行大小规定
for (int i = 0; i < N; i++)
{
cin >> v[i].ID >> v[i].score;
v[i].rank = v[i].ID[0];
v[i].room = v[i].ID.substr(1, 3);
v[i].date = v[i].ID.substr(4, 6);
v[i].id = v[i].ID.substr(10, 3);
}
//flag用于判断类别,type规定要求
int flag;
string type;
for (int i = 0; i < M; i++)
{
cin >> flag >> type;
switch (flag)
{
case 1:
cout << "Case " << i + 1 << ": " << flag << ' ' << type << endl;
case1(type);
break;
case 2:
cout << "Case " << i + 1 << ": " << flag << ' ' << type << endl;
case2(type);
break;
case 3:
cout << "Case " << i + 1 << ": " << flag << ' ' << type << endl;
case3(type);
break;
default:
cout << "Case " << i + 1 << ": " << flag << ' ' << type << endl;
cout << "NA" << endl;
break;
}
}
return 0;
}
代码:解码PAT准考证
其余内容将持续更新,欢迎交流批评指正!
2019.3.1
所有PAT乙级内容已经全部更新完,github代码全部上传完毕,并且网站所有测试全部AC通过,历时差不多不到3个月,从12月中旬,到1月(实习懈怠了很多),2月(中间放假也懈怠了许多),总体来说整个过程还是痛并快乐着,有时会为一个测试点不能通过而茶饭不思,有时看到一次提交全部AC会倍感欣慰,有时晚上睡着了还在想题,反正这个过程中,学习了很多,也通过借鉴网上各种各样的大神们的代码,看到了很多自己的不足,总而言之,学无止境,不进则退。