PTA Basic Level Practice 解题思路和代码,主要用的是 C++。每22题一篇博客,可以按目录来进行寻找。
思路:
用一个三目运算符即可。
#include
using namespace std;
int main()
{
int n, count = 0; // count 为步数
cin >> n;
while (n != 1) // n 为1时退出循环
{
n = (n % 2 == 0 ? (n / 2) : (3 * n + 1) / 2);
++count;
}
cout << count;
return 0;
}
思路:
100位的整数,内置类型 int,long int 和 long long int 都是表示不了的,所以输入的一定是字符串。题目说 n 小于 1 0 100 10^{100} 10100,表明其最多只有一百位,假设每一位都是最大的10,100个10相加都只有1000。所以 n 转换成数字最大也就是999。所以只需要判断三个位的数字即可:
#include
using namespace std;
int main()
{
int num = 0;
char c;
char s[10][10] = {"ling", "yi", "er", "san", "si", "wu", "liu", "qi", "ba", "jiu"};
while ((c = getchar()) != '\n') // 读取到回车符时结束
num += c - '0';
if (num / 100) // 判断百位数是否为0
cout << s[num / 100] << " "; // num / 100 得到的是百位数
if (num / 10) // 判断十位数是否为0
cout << s[(num % 100) / 10] << " "; // (num % 100) / 10 得到的是十位数
cout << s[num % 10]; // 个位不用判断直接输出
return 0;
}
思路:
第二点:任意形如 xPATx 的字符串都可以获得“答案正确”,其中 x 或者是空字符串,或者是仅由字母 A 组成的字符串。那么说明形如 PAT、APATA、AAPATAA 都是正确的。也就是说在 PAT 左右两侧的 A 的数量要一样多。
第三点:换个意思,在 aPbTc 是正确的基础上,aPbATca 也是正确的,其中 a、 b、 c 均或者是空字符串,或者是仅由字母 A 组成的字符串。
推了这几轮就能发现,正确的式子必须满足三点:
使用 map 容器来保存每次字符出现的次数,在枚举字符的过程中,每次遇到 P 或 T 都将位置保存在相应的变量中。最后的 if 语句中的条件分别对应上面的三点。
#include
#include
using namespace std;
int main()
{
int n, p = 0, t = 0;
cin >> n;
string s;
while (n--)
{
cin >> s;
map<char, int> mp;
for (int i = 0; i < s.size(); ++i)
{
++mp[s[i]]; // 当前字符计数加1
if (s[i] == 'P') p = i; // 用 p 保存 P 出现的位置
if (s[i] == 'T') t = i; // 用 t 保存 T 出现的位置
}
if (mp['P'] == 1 && mp['T'] == 1 && t - p > 1 && mp['A'] != 0 && mp.size() == 3 && p * (t - p - 1) == s.size() - t - 1)
cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
#include
#include
using namespace std;
int main()
{
int n, sc, sc_max = -1, sc_min = 101;
string name, id, name_max, name_min, id_max, id_min;
cin >> n;
while (n--)
{
cin >> name >> id >> sc;
if (sc > sc_max)
{
sc_max = sc;
name_max = name;
id_max = id;
}
if (sc < sc_min)
{
sc_min = sc;
name_min = name;
id_min = id;
}
}
cout << name_max << " " << id_max << endl;
cout << name_min << " " << id_min;
return 0;
}
思路:
题目的意思就是,给出一行正整数,没有出现在递推过程中的数就称为关键数字,程序要能按从大到小的顺序输出这一行正整数中的关键数字。可以先根据输入的整数建立一个 map,左值是数,右值为初始化为1表示未出现过(未被覆盖),为0表示出现过(被覆盖)。然后遍历 map,对所有整数进行一次 3n + 1 递推,这个过程中出现过的所有数字,将其存入 map 并将值置为1。最后打印所有值为1的键即可。
#include
#include
using namespace std;
int main()
{
map<int, int> hash;
int k, n;
cin >> k;
while (k--) // 循环输入 k 个正整数
{
cin >> n;
hash[n] = 1; // 初始整数的置为1,表明未被覆盖
}
for (auto it = hash.cbegin(); it != hash.cend(); ++it)
{ // 遍历 map 容器
int temp = it->first; // 保存键的数值
while (temp != 1) // temp 为1时退出循环
{
temp = temp % 2 == 0 ? (temp / 2) : (3 * temp + 1) / 2;
hash[temp] = 0; // 递推过程出现的数都是被覆盖的数,将其置为0
}
}
auto it = hash.crbegin(); // it 是指向尾元素的常迭代器
while (!it->second) ++it; // it 向容器首元素方向移动直到遇见第一个关键数字停下
cout << it++->first; // 打印第一个关键数字,后面不跟空格
for (; it != hash.crend(); ++it) // 打印剩余的所有关键数字,其前面都需要加上空格
if (it->second)
cout << " " << it->first;
return 0;
}
#include
using namespace std;
int main()
{
int n;
cin >> n;
for (int i = 0; i < n / 100; ++i) cout << 'B'; // 打印字符 B
n %= 100; // 去除百位数
for (int i = 0; i < n / 10; ++i) cout << 'S'; // 打印字符 S
n %= 10; // 去除十位数
for (int i = 1; i <= n; ++i) cout << i; // 打印个位数
return 0;
}
思路:
对于素数算法不了解的读者可以阅读这篇文章:OJ 刷题必备知识总结(二)知识点26。
#include
#include
using namespace std;
int main()
{
int n, count = 0;
cin >> n;
vector<int> prime; // prime 存储所有的素数
for (int i = 2; i <= n; ++i) // 枚举2 ~ n 的所有数
{
int flag = 1; // flag 为1表明 i 是素数
for (int j = 2; j * j <= i; ++j) // 素数判定
if (i % j == 0) // 存在除1和本身外的因子
flag = 0; // flag 置0表明其是合数
if (flag) prime.push_back(i); // flag 仍为1,将 i 加入素数表
}
for (int i = 1; i < prime.size(); ++i) // 统计符合题目要求的素数对
if (prime[i] - prime[i - 1] == 2)
++count;
cout << count;
return 0;
}
思路:
读者可以手写题给数组平移前后的数组下标对比,就可以发现其中的规律。假设初始下标应为 a,平移后的为 b,则有:
b = ( a + m ) % n b = (a + m) \% n b=(a+m)%n
取余是因为 m 有可能大于 n(第三个测试数据)。
#include
#include
using namespace std;
int main()
{
int n, m, k;
cin >> n >> m;
vector<int> vec1(n, 0); // 定义大小为 n 的数组,初始值全0
for (int i = 0; i < n; ++i)
{
cin >> k;
vec1[(i + m)% n] = k; // 输入时就移动整数到其最终位置
}
for (int i = 0; i < n - 1; ++i) // 打印整数序列
cout << vec1[i] << ' ';
cout << vec1[n - 1]; // 最后一个整数单独打印
return 0;
}
思路:
使用 while (cin >> word) 是没问题的, 因为 PAT 的输入样例都是通过文件来读取的,读到文件结尾输入流就停止了。
#include
#include
using namespace std;
int main()
{
stack<string> st;
string word;
while(cin >> word) // 循环读入每一个单词
st.push(word); // 单词入栈
if (!st.empty()) // 打印栈顶单词之前必须检查栈是否为空
cout << st.top(); // 打印栈顶单词
st.pop(); // 栈顶单词出栈
while(!st.empty()) // 栈空停止循环
{
cout << " " << st.top();
st.pop(); // 栈顶单词出栈
}
return 0;
}
思路:
题设的一个陷阱是,如果多项式只有常数项,则应当输出 “0 0”;但如果不只有常数项,则不管常数项出现在多项式的哪个位置,求导后都不进行输出(直接跳过)。
例如 3 + 4 x − 1 + 5 x − 2 3 + 4x^{-1} +5x^{-2} 3+4x−1+5x−2,输入为3 0 4 -1 5 -2,那么求导后是 − 4 x − 2 − 10 x − 3 -4x^{-2} - 10x^{-3} −4x−2−10x−3,输出应该是 -4 -2 -10 -3,开头的 “0 0” 不要打印。
关键在于 flag 的定义,一举两得,既能拿来判定输出的格式,还能保证在没有输出的时候能正确输出 “0 0”。
#include
using namespace std;
int main()
{
int n, k, flag = 0; // n 为系数,k 为指数,flag 为1表明有过输出
while (cin >> n >> k)
{
if (k != 0) // 非常数项才输出求导后的数字
{
if (flag) cout << " "; // 前面有输出过数字,则需要先打印一个空格
cout << n * k << " " << k - 1;
flag = 1; // 置 flag 为1表明有过输出
}
}
if (!flag) cout << "0 0"; // 如果没有输出过,打印 “0 0”
return 0;
}
思路:
int 型数据能表示的范围是 [ − 2 − 31 , 2 31 − 1 ] [-2^{-31}, 2^{31} - 1] [−2−31,231−1],所以 a + b 有可能超过 int 型数据所能表示的最大范围,要用 long int 来定义三个变量。
#include
using namespace std;
int main()
{
long int a, b, c, t;
cin >> t;
for (int i = 1; i <= t; ++i)
{
cin >> a >> b >> c;
if (a + b > c) cout << "Case #" << i << ": true" << endl;
else cout << "Case #" << i << ": false" << endl;
}
return 0;
}
思路:
题目不难,多写几个 if else 也能达到题目的要求。难点在于如何利用 C++ 的知识来解决。我采用了柳神的想法,将对应余数的数字存到对应的数组中,再集中进行处理。代码中要注意的一点是除5余1的情况,因为 j 是从0开始的,除2余数为0是加,除2余数为1才是减。
#include
#include
using namespace std;
int main()
{
vector<int> vec[5]; // 定义一个二维数组
int n, k, sum1 = 0, sum2 = 0, sum3 = 0, max = 0;
scanf("%d", &n);
while (n--) // 循环输入 n 个数
{
scanf("%d", &k);
vec[k % 5].push_back(k); // 根据余数添加到对应的数组
}
for (int i = 0; i < 5; ++i) // 枚举二维数组中的所有数字
{
for (int j = 0; j < vec[i].size(); ++j)
{
if (i == 0 && vec[i][j] % 2 == 0) sum1 += vec[i][j]; // 求出除5余2的所有偶数的和
if (i == 1 && j % 2 == 0) sum2 += vec[i][j]; // j 从0开始的,所以要注意判定条件
if (i == 1 && j % 2 == 1) sum2 += -vec[i][j];
if (i == 3) sum3 += vec[i][j]; // 求出除5余3的所有数的和
if (i == 4 && vec[i][j] > max) max = vec[i][j]; // 求出除5余4的数字中的最大值
}
}
for (int i = 0; i < 5; ++i)
{
if (i != 0) printf(" "); // 只有 A1 不输出前面的空格
if (i == 0 && sum1 == 0 || i != 0 && vec[i].size() == 0) printf("N"); // 分类中不存在数字,输出 N
else
{
if (i == 0) printf("%d", sum1);
if (i == 1) printf("%d", sum2);
if (i == 2) printf("%d", vec[2].size());
if (i == 3) printf("%.1f", (sum3 * 1.0) / vec[3].size()); // 求平均数,记得乘上1.0转为浮点数
if (i == 4) printf("%d", max);
}
}
return 0;
}
思路:
这一题不能按照常规思路来——即找出某个范围内的所有素数,然后再去输出 P M P_M PM 到 P N P_N PN 之间的素数。因为 N 或许会很大从而出现段错误;如果直接定义一个很大的范围寻找素数还可能会导致超时,并且也并不清楚第10000个素数究竟是到多大了。
不妨这么做:从 i = 2 开始枚举所有数字,每一轮都判断 i 是否是素数,用变量 count 来标记 i 是第几个素数,一直找到 count = N - 1 时停下来,只打印 count >= M 后的所有素数。
#include
using namespace std;
int main()
{
int m, n, count = 0;
cin >> m >> n;
for (int i = 2; count < n; ++i) // 因为 count 从0开始递增,所以要小于 n
{
int flag = 1; // flag 为1表明 i 是素数
for (int j = 2; j * j <= i; ++j) // 该循环判断 i 是否是素数
if (i % j == 0) flag = 0;
if (flag) ++count; // i 是素数,count 计数值+1
if (count >= m && flag) // 如果 count 比 m 大且 i 是素数就需要输出
{
if ((count - m + 1) % 10 != 1) cout << " "; // 每行的第一个数字前没有空格
cout << i;
if ((count - m + 1) % 10 == 0) cout << endl; // 每行的最后一个数字后要换行
}
}
return 0;
}
思路:
#include
#include
using namespace std;
int main()
{
string s[7] = {"MON","TUE","WED","THU","FRI","SAT","SUN"}, s1, s2, s3, s4;
cin >> s1 >> s2 >> s3 >> s4;
int i = 0;
for (; i < s1.size() && i < s2.size(); ++i) // 寻找前面两个字符串中第一对相同的大写字母
{
if (s1[i] == s2[i] && 'A' <= s1[i] && s1[i] <= 'G')
{
cout << s[s1[i] - 'A'] << " "; // 不要忘记输出后面的空格
break; // 退出循环
}
}
++i; // 递增一下 i,要不然在下面的循环中又被输出了
for (; i < s1.size() && i < s2.size(); ++i) // 寻找前面两个字符串中第二对相同的大写字母或数字字符
{
if (s1[i] == s2[i] && (isdigit(s1[i]) || 'A' <= s1[i] && s1[i] <= 'N'))
{ // 数字字符与大写字母的输出不一样,注意输出格式,小时和分钟都要占用两位
isdigit(s1[i]) ? printf("%02d:", s1[i] - '0') : printf("%02d:", s1[i] - 'A' + 10);
break; // 退出循环
}
}
for (i = 0; i < s3.size() && i < s4.size(); ++i)
{
if (s3[i] == s4[i] && isalpha(s3[i])) // 寻找后面两个字符串中第一对相同的字母
{
printf("%02d", i);
break; // 退出循环
}
}
return 0;
}
思路:
这一题一定要多读几遍,然后在草稿纸上写出分类的标准。先思考出一个最初的解决方案,再一步步地进行优化。整个题目主要分两个部分,一个部分是根据成绩区分四类考生,另一个部分是根据成绩进行排序。
最开始我的想法是统一存入一个 vector 中,然后利用 sort 来排序。但是发现在 compare 函数中就需要写很多的代码:不仅要区分四类考生,在每类考生下又进行排序。而在 main 函数中我还是要做同样的工作,所以我索性把排序和分类彻底分开。我将排序提取出来,让 compare 函数只负责排序。然后只在 main 函数中进行分类,在输入数据的时候就直接按照分数分入不同的 vector 中,输出时对每个 vector 应用 sort 后再输出即可。
经验总结:
如果使用 cin 的话还需要在 stu 结构体中写构造函数才能初始化相关变量:
struct stu
{
int id, d_score, c_score, t_score;
stu(int id, int d_score, int c_score)
{
this->id = id;
this->d_score = d_score;
this->c_score = c_score;
this->t_score = d_score + c_score;
}
};
...
cin >> i >> d >> c;
struct stu temp(i, d, c);
用 scanf 就可以直接一条语句完成初始化。:
struct stu
{
int id, d_score, c_score, t_score;
};
...
struct stu temp;
scanf("%d%d%d", &temp.id, &temp.d_score, &temp.c_score);
最后的 AC 代码如下:
#include
#include
#include
using namespace std;
struct stu
{ // 考生结构体,包含准考证号、德分和才分成员变量
int id, d, c;
};
bool compare(struct stu p, struct stu q)
{
if (p.d + p.c != q.d + q.c) // 总分不相同时,总分高的排在前面
return (p.d + p.c) > (q.d + q.c);
else if (p.d != q.d) return p.d > q.d; // 总分不相同时,德分高的排在前面
else return p.id < q.id; // 总分和德分相同的话按照准考证号升序排列
}
int main()
{
int m, l, h;
vector<stu> vec[4]; // 定义一个 vec 数组,每个元素都是一个 vector
scanf("%d%d%d", &m, &l, &h); // 输入考生人数、录取最低分和优先录取线
while (m--)
{
struct stu temp; // temp 存储考生的准考证号、德分和才分
scanf("%d%d%d", &temp.id, &temp.d, &temp.c);
if (temp.d >= l && temp.c >= l)
{ // 筛选掉德才分均未过最低录取线的考生
if (temp.d >=h && temp.c >= h) vec[0].push_back(temp); // 第一类考生
else if (temp.d >= h && temp.c < h) vec[1].push_back(temp); // 第二类考生
else if (temp.d < h && temp.c < h && temp.d >= temp.c) vec[2].push_back(temp); // 第三类考生
else if (temp.d < h) vec[3].push_back(temp); // 第四类考生
}
}
printf("%d\n", vec[0].size() + vec[1].size() + vec[2].size() + vec[3].size()); // 一行打印考生人数
for (int i = 0; i < 4; ++i)
{ // 对每一类考生进行排序并进行输出
sort(vec[i].begin(), vec[i].end(), compare);
for(int j = 0; j < vec[i].size(); ++j)
printf("%d %d %d\n", vec[i][j].id, vec[i][j].d, vec[i][j].c);
}
return 0;
}
思路:
1 0 9 10^9 109 大小的数据需要用 long long int 来定义,另外 P A P_A PA和 P B P_B PB也需要,因为有可能 A 和 B 的所有位数都相同。
#include
using namespace std;
int main()
{
long long int a, da, b, db, pa = 0, pb = 0;
cin >> a >> da >> b >> db;
while (a)
{
if (a % 10 == da) pa = pa * 10 + da;
a /= 10;
}
while (b)
{
if (b % 10 == db) pb = pb * 10 + db;
b /= 10;
}
cout << pa + pb;
return 0;
}
思路:
经验总结:
#include
using namespace std;
int main()
{
string a;
int b, flag = 0, r = 0; // r 为余数,同时也保存当前的被除数
cin >> a >> b;
for (int i = 0; i < a.size(); ++i)
{
r = r * 10 + a[i] - '0'; // 余数乘10加到该位上
if (r >= b) // 够除
{
cout << r / b; // 输出该位的商
r = r % b; // 计算余数
flag = 1; // flag = 1 表明高位输出过非零数
}
else // 不够除,则该位商为0
if (flag) cout << "0"; // 高位没输出过非零数,不需要输出0
}
if (!flag) cout << "0"; // 商为0需要打印
cout << " " << r; // 余数为0的话也需要打印
return 0;
}
思路:
a,b 分别表示每一次甲乙给出的手势,jia_win[3] 和 yi_win[3] 数组分别统计甲乙某个手势获胜的次数,下标012分别表示 BCJ,maxJ 和 maxY 分别表示甲乙获胜最多的手势对应的下标。由于获胜次数相同时按字典序输出,所以要先比较 B 和 C ,最后再比较 J。
#include
using namespace std;
int main()
{
char a, b, c[4] = {"BCJ"};
int n, tie = 0, jia_win[3] = {0}, yi_win[3] = {0};
cin >> n;
while (n--)
{
cin >> a >> b;
if (a == b) ++tie; // 平局计数+1
else if (a == 'B' && b == 'C') ++jia_win[0]; // 甲用布赢计数+1
else if (a == 'C' && b == 'J') ++jia_win[1]; // 甲用锤赢计数+1
else if (a == 'J' && b == 'B') ++jia_win[2]; // 甲用剪赢计数+1
else if (b == 'B' && a == 'C') ++yi_win[0]; // 乙用布赢计数+1
else if (b == 'C' && a == 'J') ++yi_win[1]; // 乙用锤赢计数+1
else if (b == 'J' && a == 'B') ++yi_win[2]; // 乙用剪赢计数+1
}
cout << jia_win[0] + jia_win[1] + jia_win[2] << " " << tie << " " << yi_win[0] + yi_win[1] + yi_win[2] << endl;
cout << yi_win[0] + yi_win[1] + yi_win[2] << " " << tie << " " << jia_win[0] + jia_win[1] + jia_win[2] << endl;
int maxJ = jia_win[0] >= jia_win[1] ? 0 : 1; // 寻找甲获胜最多的手势
maxJ = jia_win[maxJ] >= jia_win[2] ? maxJ : 2;
int maxY = yi_win[0] >= yi_win[1] ? 0 : 1; // 寻找乙获胜最多的手势
maxY = yi_win[maxY] >= yi_win[2] ? maxY : 2;
cout << c[maxJ] << " " << c[maxY];
return 0;
}
思路:
用字符串来直接读取输入,因为 sort 函数,可以很方便的实现重新排列。将其转换为数字求差,再将差转换为字符串,然后按照格式输出即可。
经验总结:
#include
#include
using namespace std;
bool cmp(char a, char b) { return a > b; }
int main()
{
string s, big, small, result;
cin >> s;
s.insert(0, 4 - s.size(), '0'); // 用0补齐头部的空缺字符
do
{
big = small = s;
sort(big.begin(), big.end(), cmp); // 从大到小排序得到递减序列
sort(small.begin(), small.end()); // 从小到大排序得到递增序列
int a = stoi(big), b = stoi(small); // 将递减序列和递增序列转换为对应的整数
s = to_string(a - b); // 将它们的差转回字符串保存在 s 中
s.insert(0, 4 - s.size(), '0'); // 用0补齐头部的空缺字符
cout << big << " - " << small << " = " << s << endl;
} while (s != "6174" && s != "0000"); // s 为数字黑洞或0时退出循环
return 0;
}
思路:
由描述很容易知道,最大收益策略就是先把单价高的月饼卖掉,所以自然而然地想到要用上 sort 排序函数。有三个维度的数据(第三个数据 “单价” 需要单独计算),又高度绑定在一起,所以最好的办法是定义在一个结构体中。然后在 sort 函数中根据单价的大小关系进行排序。
经验总结:
sum += vec[i].unit * d;
// 上述语句等价于下面的
sum += vec[i].price / vec[i].store * d;
if (d >= vec[i].store)
{
sum += vec[i].price;
d -= vec[i].store;
}
else
{
sum += vec[i].unit * d;
d = 0;
}
// 上面的 if-else 等价于下面的两个三元运算
sum = d >= vec[i].store ? sum + vec[i].price : sum + vec[i].unit * d;
d = d >= vec[i].store ? d - vec[i].store : 0;
AC 代码如下:
#include
#include
#include
using namespace std;
struct mooncake
{
double store, price, unit; // 月饼结构体,成员变量分别为库存、总售价和单价
};
bool cmp(mooncake a, mooncake b) { return a.unit > b.unit; }
int main()
{
int n, d;
cin >> n >> d;
vector<mooncake> vec(n);
for (int i = 0; i < n; ++i) scanf("%lf", &vec[i].store);
for (int i = 0; i < n; ++i) scanf("%lf", &vec[i].price);
for (int i = 0; i < n; ++i)
vec[i].unit = vec[i].price / vec[i].store; // 计算每种月饼的单价
sort(vec.begin(), vec.end(), cmp); // 按单价从高到低排序
double sum = 0.0; // 定义最大收益
for (int i = 0; i < n; ++i)
{ // 用两个三元运算符来计算当前的价格
sum = d >= vec[i].store ? sum + vec[i].price : sum + vec[i].unit * d;
d = d >= vec[i].store ? d - vec[i].store : 0;
}
printf("%.2f", sum);
return 0;
}
#include
using namespace std;
int main()
{
string s;
cin >> s;
int digit[10] = {0};
for (int i = 0; i < s.size(); ++i)
++digit[s[i] - '0'];
for (int i = 0; i < 10; ++i)
if (digit[i])
cout << i << ":" << digit[i] << endl;
return 0;
}
思路:
请读者参考OJ 刷题必备知识总结(一)知识点20、进制转换。
#include
#include
using namespace std;
int main()
{
int a, b, c, d;
vector<int> vec;
cin >> a >> b >> d;
c = a + b;
do // 此处只能用 do...while,不能用 while,详细请参考博客
{
vec.push_back(c % d);
c /= d;
} while (c);
for (int i = vec.size() - 1; i >= 0; --i)
cout << vec[i];
return 0;
}
希望本篇博客能对你有所帮助,也希望看官能动动小手点个赞哟~~。