题目1:A+B格式(选自AcWing 1473)
题目描述:
计算 a+b并以标准格式输出总和----也就是说,从最低位开始每隔三位数加进一个逗号(千位分隔符),如果结果少于四位则不需添加。
输入格式
共一行,包含两个整数 a和 b。
输出格式
共一行,以标准格式输出 a+b的和。
数据范围
−10^6 < a, b < 10^6
输入样例:
-1000000 9
输出样例:
-999,991
代码:
#include<iostream>
using namespace std;
int main(){
int a, b;
cin >> a >> b;
int c = a + b;
string num = to_string(c);
string res;
for(int i = num.size() - 1, j = 0;i >= 0;i --)
{
res = num[i] + res;
j ++;
if(j % 3 == 0 && i && num[i-1] != '-')
{
res = ',' + res;
}
}
cout << res << endl;
return 0;
}
思路:
模拟即可,先求和再解析字符串
注意点:
①整数转string直接调用to_string()函数即可
②字符串拼接时是从后往前拼接,故应为res = ',' + res 而不是res += ','
题目2: 拼写正确(选自AcWing 1477)
题目描述:
给定一个非负整数 N,你的任务是计算 N的所有数字的总和,并以英语输出总和的每个数字。
输入格式
共一行,包含一个整数 N。
输出格式
共一行,用英语输出总和的每个数字,单词之间用空格隔开。
数据范围
0 ≤ N ≤ 10^100
输入样例:
12345
输出样例:
one five
代码:
#include<iostream>
using namespace std;
int main(){
string n;
cin >> n;
int sum = 0;
for(auto c : n){
sum += c - '0';
}
string res = to_string(sum);
char word[10][10] = {
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
}; //存放单词数组
cout << word[res[0] - '0'];
for(int i = 1; i < res.size(); i++){
cout << ' ' << word[res[i] - '0'];
}
return 0;
}
思路:
模拟,同问题1,先求和再解析字符串
注意点:
采用二维数组存放单词,输出时数字的偏移量即对应二维数组内对应单词的下标,避免复杂的if条件语句的堆叠,代码更加美观
题目3:签到与签出(选自AcWing 1478)
题目描述:
每天第一个到机房的人负责开门,最后一个从机房离开的人负责锁门。
现在,给定每个人的签到与签出记录,请你找出当天开门的人以及锁门的人分别是谁。
输入格式
第一行包含整数 M,表示共有 M 个人的签到签出记录。
接下来 M行,每行的形式如下:
ID_number Sign_in_time Sign_out_time
时间以 HH:MM:SS
形式给出,ID_number
是一个长度不超过 1515 的字符串。
输出格式
共一行,输出开门人和锁门人的ID_number
,用一个空格隔开。
数据范围
1 ≤ M ≤ 10,
数据保证每个人的签到时间早于签出时间,并且不会出现两个人同时签到或同时签出的情况。
输入样例:
3
CS301111 15:30:28 17:00:10
SC3021234 08:00:00 11:25:25
CS301133 21:45:00 21:58:40
输出样例:
SC3021234 CS301133
代码:
#include<iostream>
using namespace std;
int main(){
string open_id, open_time;
string close_id, close_time;
int m;
cin >> m;
for(int i = 0; i < m; i++){
string id, in_time, out_time;
cin >> id >> in_time >> out_time;
if(i == 0 || in_time < open_time){
open_id = id;
open_time = in_time;
}
if(i == 0 || out_time > close_time){
close_id = id;
close_time = out_time;
}
}
cout << open_id << ' '<< close_id;
return 0;
}
思路:
维护最早开门的人的id和time以及最晚锁门的人的id和time,遍历比较模拟即可
注意点:
比较时间时,由于时间的位数一致,比较时间可直接比较字符串的字典序(即直接进行字符串大小比较即可),仅适用于字符串位数一致的情况
题目:4:密码(选自AcWing 1519)
题目描述:
为了准备 PAT,系统不得不为用户生成随机密码。
但是有时一些数字和字母之间总是难以区分,比如 1
(数字一)和 l
(L 的小写),0
(数字零)和 O
(o 的大写)。
一种解决办法是将 1
(数字一)替换为 @
,将 0
(数字零)替换为 %
,将 l
(L的小写)替换为 L
,将 O
(o的大写)替换为 o
。
现在,你的任务就是帮助系统检查这些用户的密码,并对难以区分的部分加以修改。
输入格式
第一行包含一个整数 N,表示用户数量。
接下来 N 行,每行包含一个用户名和一个密码,都是长度不超过 1010 且不含空格的字符串。
输出格式
首先输出一个整数 M,表示已修改的用户密码数量。
接下来 M行,每行输出一个用户名称和其修改后的密码。
用户的输出顺序和读入顺序必须相同。
如果没有用户的密码被修改,则输出 There are N accounts and no account is modified
,其中 N是用户总数。
如果 N=1,则应该输出 There is 1 account and no account is modified
。
数据范围
1≤N≤1000
输入样例1:
3
Team000002 Rlsp0dfa
Team000003 perfectpwd
Team000001 R1spOdfa
输出样例1:
2
Team000002 RLsp%dfa
Team000001 R@spodfa
输入样例2:
1
team110 abcdefg332
输出样例2:
There is 1 account and no account is modified
输入样例3:
2
team110 abcdefg222
team220 abcdefg333
输出样例3:
There are 2 accounts and no account is modified
代码:
#include <iostream>
using namespace std;
const int N = 1010;
string name[N], pwd[N];
//修改密码函数
string change(string str){
string res;
for(auto c : str){
if (c == '1') res += '@';
else if (c == '0') res += '%';
else if (c == 'l') res += 'L';
else if (c == 'O') res += 'o';
else res += c;
}
return res;
}
int main(){
int n;
int m = 0; //记录修改密码的用户个数
cin >> n; //输入用户的个数
for(int i = 0; i < n; i ++){
string cur_id, cur_pwd;
cin >> cur_id >> cur_pwd;
string res_pwd = change(cur_pwd);
if (res_pwd != cur_pwd){
name[m] = cur_id;
pwd[m] = res_pwd;
m++;
}
}
//特判
if(m == 0){
if(n == 1) puts("There is 1 account and no account is modified");
else{
printf("There are %d accounts and no account is modified", n);
}
}
else{
cout << m << endl;
for(int i = 0; i < m; i++){
cout << name[i] << ' ' << pwd[i] << endl;
}
}
}
思路:
模拟
注意点:
注意临界情况
题目5:男孩女孩(选自AcWing 1520)
题目描述:
给定 N个学生的成绩信息,请你求出女生第一名与男生倒数第一名的分数差距。
输入格式
第一行输入整数 N,表示学生数量。
接下来 N行,每行包含一个学生的姓名,性别,ID和成绩。其中姓名和ID是长度不超过 1010 且不包含空格的字符串。性别为 F(女)或 M(男)。成绩是一个范围在 [0,100][0,100] 的整数。保证所有学生的成绩互不相同。
输出格式
输出共三行。
第一行输出女生第一名的姓名和ID。
第二行输出男生倒数第一名的姓名和ID。
第三行输出女生第一名的成绩减去男生倒数第一名的成绩的差。
如果不存在某个性别的学生,则在对应行输出 Absent
。
在第三行输出 NA
。
数据范围
1≤N≤101
输入样例1:
3
Joe M Math990112 89
Mike M CS991301 100
Mary F EE990830 95
输出样例1:
Mary EE990830
Joe Math990112
6
输入样例2:
1
Jean M AA980920 60
输出样例2:
Absent
Jean AA980920
NA
代码:
#include <iostream>
using namespace std;
int main(){
string girl_name, girl_id;
int girl_score; //第一名女同学的姓名,id,得分
string boy_name, boy_id;
int boy_score; //最后一名男同学的姓名,id,得分
int n;
cin >> n; //总人数
for (int i = 0; i < n; i++) {
string name, sex, id;
int score;
cin >> name >> sex >> id >> score;
if (sex == "F"){ //输入信息为女同学
if (girl_name.empty() || score > girl_score) { //更新第一名女同学信息
girl_name = name;
girl_id = id;
girl_score = score;
}
}
else { //输入信息为男同学
if (boy_name.empty() || score < boy_score) { //更新最后一名男同学信息
boy_name = name;
boy_id = id;
boy_score = score;
}
}
}
if (girl_name.empty()) cout << "Absent" << endl;
else cout << girl_name << ' ' << girl_id << endl;;
if (boy_name.empty()) cout << "Absent" << endl;
else cout << boy_name << ' ' << boy_id << endl;
if (girl_name.size() && boy_name.size()) cout << girl_score - boy_score;
else cout << "NA";
return 0;
}
题目6:字符串减法(选自AcWing 1534)
题目描述:
给定两个字符串 S1和 S2,S=S1−S2 定义为将 S1 中包含的所有在 S2中出现过的字符删除后得到的字符串。
你的任务就是计算 S1−S2。
输入格式
共两行,第一行包含字符串 S1,第二行包含字符串 S2。
输出格式
输出共一行,表示 S1−S2的结果。
数据范围
两个给定字符串的长度都不超过 10^4。
输入样例:
They are students.
aeiou
输出样例:
Thy r stdnts.
代码:
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
string s1, s2;
getline (cin, s1); //输入的字符串可能含有空格,用getline输入
getline (cin, s2);
unordered_set <char> hash; //定义哈希表,unordered_set只存储元素的值(不以键值对存),且不重复
for (auto c : s2) hash.insert(c);
string res;
for (auto a : s1) {
if (hash.count(a) == 0) { //未在s2中出现,加入结果
res += a;
}
}
cout << res;
return 0;
}
思路:
如果直接对两个字符串进行遍历,则时间复杂度为O(n²),极有可能超时,判断一个字符串是否有字符在第二个字符串中出现,可采用哈希表的思想,哈希表增删查改的时间复杂度均为O(1),提高了程序的运行效率。
补充知识点:
①输入问题
本题输入的字符串可能含有字符串,直接用cin>>输入可能会报错,可采用getline(cin, str)方式输入
②STL中set和map小结
(1)
基于平衡树实现: O(logn)
有序
set<int> a;
map<int, int> b;
multiset<int> c;
multimap<int,int> d;
区别,multiset和multimap允许key重复,而set和map不允许重复元素
(2)
基于哈希表实现:O(1)
无序
unordered_set <int> e;
unordered_map <int, int> f;
unordered_multiset <int> g;
unordered_multimap <int, int> h;
区别同上
题目7:说话方式(选自AcWing 1557)
题目描述:
不同的人对描述同一种事物的同义词的偏爱程度可能不同。
例如,在说警察时,有人喜欢用 the police
,有人喜欢用 the cops
。
分析说话方式有助于确定说话者的身份,这在验证诸如和你线上聊天的是否是同一个人十分有用。
现在,给定一段从某人讲话中提取的文字,你能确定他的最常用词吗?
输入格式
输入共一行,包含一个字符串,以回车符 \n
终止。
输出格式
共一行,输出最常用词以及其出现次数。
如果常用词有多个,则输出字典序最小的那个单词。
注意,单词在输出时,必须全部小写。
单词是指由连续的字母和数字构成的,被非字母数字字符或行首/行尾分隔开的,连续序列。
单词不区分大小写。
数据范围
输入字符串长度不超过 1048576,且至少包含一个大小写字母或数字。
输入样例:
Can1: "Can a can can a can? It can!"
输出样例:
can 5
代码:
#include <iostream>
#include <ctype.h>
#include <unordered_map>
using namespace std;
//判断一个字符是否在一个单词内
bool check_word (char c) {
if (c > '0' && c < '9') return true;
if (c > 'a' && c > 'z') return true;
if (c > 'A' && c < 'Z') return true;
return false;
}
int main() {
string str;
getline (cin, str);
unordered_map <string, int> hash; //存放单词及其次数
//双指针遍历寻找每个单词
for (int i = 0; i < str.size(); i ++) {
string res;
for (int j = i; j < str.size(); j ++) {
if (check_word(j)) res += tolower(str[j]); //若有大写字母,则转小写
else break; //遍历到分隔符,跳出循环,当前单词遍历结束
}
i = j;
hash[res] += 1;
}
//遍历哈希表,寻找出现次数最多的单词
string word;
int cnt = -1;
for (auto item : hash) {
if (item.second > cnt || (item.second == cnt && item.first < word)) {
word = item.first;
cnt = item.second;
}
}
cout << word << ' ' << cnt;
return 0;
}
思路:
本题需要从一个字符串中寻找每个单独的单词,将其存入哈希表,遍历字符串寻找单独单词的过程采用双指针可以提高效率。
双指针的核心思想如下:
外层指针i遍历整个字符串,内层指针j从当前i的位置开始向后遍历,遍历到单词的末尾结束,存储当前单词,并且将i指针移到j指针位置即可
注意:
大写字母转小写字母的两个方法:
①直接调用tolower()函数,头文件为ctype.h
②a的ASCII码为97,A的ASCII码为65,相差32,故可直接通过字符串+32的方式进行转换
总结:
字符串问题主要用模拟的思想,核心实质上是一个遍历的过程,要合理利用C++ STL的一些容器简化代码,以及掌握常见的降低时间复杂度的方法。