原题链接 P1957 口算练习题

6.字符串与文件操作

字符数组

例6-4:口算练习题

题目描述
王老师正在教简单算术运算。细心的王老师收集了i道学生经常做错的口算题,并且想整理编写成一份练习。 编排这些题目是一件繁琐的事情,为此他想用计算机程序来提高工作效率。王老师希望尽量减少输入的工作量,比如 5+8\texttt{5+8} 的算式最好只要输入 5\texttt 58\texttt 8,输出的结果要尽量详细以方便后期排版的使用,比如对于上述输入进行处理后输出 5+8=13\texttt{5+8=13} 以及该算式的总长度 66。王老师把这个光荣的任务交给你,请你帮他编程实现以上功能。
输入格式
第一行为数值 ii
接着的 ii 行为需要输入的算式,每行可能有三个数据或两个数据。
若该行为三个数据则第一个数据表示运算类型,a\texttt a 表示加法运算,b\texttt b 表示减法运算,c\texttt c 表示乘法运算,接着的两个数据表示参加运算的运算数。
若该行为两个数据,则表示本题的运算类型与上一题的运算类型相同,而这两个数据为运算数。
输出格式
输出 2×i2\times i 行。对于每个输入的算式,输出完整的运算式及结果,第二行输出该运算式的总长度
样例 #1
样例输入 #1

1
2
3
4
5
4
a 64 46
275 125
c 11 99
b 46 64

样例输出 #1

1
2
3
4
5
6
7
8
64+46=110
9
275+125=400
11
11*99=1089
10
46-64=-18
9

提示
数据规模与约定
对于 50%50\% 的数据,输入的算式都有三个数据,第一个算式一定有三个数据。
对于所有数据,0<i500<i\leq 50,运算数为非负整数且小于 1000010000

官方题解:(有误)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
int n, a, b, c;
char last, s[20], ans[20];
scanf("%d\n", &n);
while (n--)
{
fgets(s, sizeof(s), stdin); // 读入一行
if (s[0] == 'a' || s[0] == 'b' || s[0] == 'c')
last = s[0];
s[0] = ' '; // 获取计算符号,并替换为空格
sscanf(s, "%d %d", &a, &b); // 从这个字符串里面读出两个数a和b
switch (last)
{
case 'a':
c = a + b;
sprintf(ans,"%d+%d=%d", a, b, c);
break; //+
case 'b':
c = a - b;
sprintf(ans,"%d-%d=%d", a, b, c);
break; //-
case 'c':
c = a * b;
sprintf(ans,"%d*%d=%d", a, b, c);
break; //+
}
printf("%s\n%d\n", ans, strlen(ans)); // 输出
}
}
通过代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <cstdio>
#include <cstring>
#define LOCAL
using namespace std;
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
char a[10], ans[100];
char temp;
int n, x, y;
scanf("%d", &n);
while (n--)
{
scanf("%s", a);
if (a[0] >= 'a' && a[0] <= 'c')
{
temp = a[0];
scanf("%d %d", &x, &y);
}
else
{
x = atoi(a);
scanf("%d", &y);
}
memset(ans,0,sizeof(ans));//清空原有的字符串,防止长度判断错误
switch (temp)
{
case 'a':
sprintf(ans, "%d+%d=%d", x, y, x + y);
break;
case 'b':
sprintf(ans, "%d-%d=%d", x, y, x - y);
break;
case 'c':
sprintf(ans, "%d*%d=%d", x, y, x * y);
break;
}
printf("%s\n%d\n", ans, strlen(ans));
}
}

新知识

  • scanf遇到空格换行停止读入
  • sscanf(s,“%d”,&a);就可以从字符串s中读入一个整数a。
  • sprintf将数据输入到字符串,sprintf(ans, “%d*%d=%d”, x, y, x * y);就是以%d*%d=%d的格式输入到字符串ans中ans存储x*y=xy。
  • atoi ((表示 ascii to integer)是把字符串转换成整型数的一个函数)头文件为#include<stdlib.h>

string类型字符串(STL)

[例6]文字处理软件

题目描述
你需要开发一款文字处理软件。最开始时输入一个字符串作为初始文档。可以认为文档开头是第 00 个字符。需要支持以下操作:

  • 1 str:后接插入,在文档后面插入字符串 str\texttt{str},并输出文档的字符串。
  • 2 a b:截取文档部分,只保留文档中从第 aa 个字符起 bb 个字符,并输出文档的字符串。
  • 3 a str:插入片段,在文档中第 aa 个字符前面插入字符串 str\texttt{str},并输出文档的字符串。
  • 4 str:查找子串,查找字符串 str\texttt{str} 在文档中最先的位置并输出;如果找不到输出 1-1
    为了简化问题,规定初始的文档和每次操作中的 str\texttt{str} 都不含有空格或换行。最多会有 qq 次操作。
    输入格式
    第一行输入一个正整数 qq,表示操作次数。
    第二行输入一个字符串 str\texttt{str},表示最开始的字符串。
    第三行开始,往下 qq 行,每行表示一个操作,操作如题目描述所。
    输出格式
    一共输出 nn 行。
    对于每个操作 1,2,31,2,3,根据操作的要求输出一个字符串。
    对于操作 44,根据操作的要求输出一个整数。
    样例 #1
    样例输入 #1
1
2
3
4
5
6
4
ILove
1 Luogu
2 5 5
3 3 guGugu
4 gu

样例输出 #1

1
2
3
4
ILoveLuogu
Luogu
LuoguGugugu
3

提示
数据保证,1q1001 \leq q\le 100,开始的字符串长度 100\leq 100

AC代码(官方)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s, a;
int opt, q, begin, end;
cin >> q;
cin >> s;
while (q--)
{
cin >> opt;
if (opt == 1)
{
cin >> a;
s.append(a);
cout << s << endl;
}
else if (opt == 2)
{
cin >> begin >> end;
s = s.substr(begin, end);
cout << s << endl;
}
else if (opt == 3)
{
cin >> begin >> a;
s = s.insert(begin, a);
cout << s << endl;
}
else if (opt == 4)
{
cin >> a;
cout << (int)s.find(a) << endl;
}
}
return 0;
}
string类型

1.引入头文件 #include <string>
2.初始化string s
注:string与字符数组相类似,从存储从0,1,2,3…开始。
区别string可以直接复制常量也可以相互赋值,字符数组不能。

1
2
3
4
5
string a,b;
a="LUOGU"
b=a;
//但是如果是字符数组
char a="abc"是错误的。因为字符数组名a,本质上是一个地址,不可以用来直接复制和相互赋值。

3.常用操作

  • s+=str或s.append(str):在字符串s后拼接字符串str。
  • s < str:比较字符串s的字典序是否在字符串str之前
  • s.size()或s.length():得到字符串s的长度
  • s.substr(pos,len)以字符串数组来理解
1
2
3
4
5
6
string a="acwing"
cout << a.substr(0, 3) << endl; //acw,当第一个数是0 则后一位数:输出从头开始的长度为3的子串
cout << a.substr(1, 3) << endl; //cwi,当第一个数是1 则输出下标为1 到下标为3的子串
cout << a.substr(0, 9) << endl;//acwing如果超出长度范围 则输出原子串
cout << a.substr(1) << endl; //cwing,从下标为1开始输出
cout << a.substr(0) << endl; //acwing原子串
  • s.insert

str1(被插入字符串).insert(pos(插入位置),str2(被插入字符串),n ,m)
ps:n,m分别是插入字符串要截取的(真正要插入的部分)即在str2.n位置数m个,不写这个的话就是将str2整个全部插入。
如题中代码s.insert(1,a)省略n,m将字符串a全部插入
【注】在输入的位置之前插入,如0,在开头插入,1插在第二个位置,下方代码有存疑还未解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
string a;//区别:string a=" ";char a='';
// 尾插一个字符
a.push_back('a');
cout<<a<<endl;//输出a;
// insert(pos,char):在制定的位置pos前插入字符char
a.insert(a.begin(),'1'); //输出 1a
/*【注】此处存疑a.insert(a.begin(),'1');可以后面只能是一个字节的char类型,字符串和string类型;前面也不能把a.begin()改成数字0等表示插入位置。*/
//插入字符串
string str2="hello";
string s2="weakhaha";
str2.insert(0,s2,1,3);
cout<<str2<<endl;//eakhello,将字符串s2从下标为1的e开始数3个字符,分别是eak,插入原串的下标为0的字符h前
/*-------------------------------------*/
string str2="hello";
string s2="weakhaha";
str2.insert(1,s2,1,3);
cout<<str2<<endl;//heakello将字符串s2从下标为1的e开始数3个字符,分别是eak,插入原串的下标为1的字符e前
  • s.find(str,[pos]):在字符串第pos个字符开始寻找str,并返回位置,如果找不到返回-1.pos可以省略,默认值是0。
    【注】使用find函数查找子串但是找不到时,它会返回一个常数string::npos,但是由于它不一定是一个int类型的常量,所以需要将其强制转换为int才能直接输出-1(读者可以试一下直接使用cout输出,这个数字会是什么)

此处内容参考
【C++】算法竞赛常用STL万字总结
指定位置插入字符串(c++insert函数、find函数使用)
C++中的find函数用法

第9章排序

经典排序方法

冒泡排序

快速排序

P1923 【深基9.例4】求第 k 小的数 ,快排优化还没想明白

插入排序

希尔排序

归并排序

选择排序

堆排序

基数排序

桶排序

STL

sort函数

简介

需要引入头文件#include< algorithm >

sort()函数是类似于快速排序的方法,时间复杂度为n*log2(n),执行效率较高。并非只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序和堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。当数据量较大时采用快速排序,分段递归。一旦分段后的数据量小于某个阀值,为避免递归调用带来过大的额外负荷,便会改用插入排序。而如果递归层次过深,有出现最坏情况的倾向,还会改用堆排序。所以说sort()是一个比较灵活的函数,它也会根据我们数据的需要进行排序,所以我们就不用担心以上的问题了。对于大部分的排序需求,sort()都是可以满足的。

1
sort(begin, end, cmp);

其中begin为指向待sort()的数组的第一个元素的指针,end为指向待sort()的数组的最后一个元素的下一个位置的指针,cmp参数为排序准则,cmp参数可以不写,如果不写的话,默认从小到大进行排序。
如果我们想从大到小排序可以将cmp参数写为 greater<int>() 就是对int数组进行排序,例如 sort(a,a+n,greater<int>());

当然<>中我们也可以写double、long、float等等。

自定义排序标准

也可以以自定义排序准则的形式来写cmp函数如

1
2
3
bool cmp(int a,int b){
return a>b;
}

可能你有点懵,不知道这是什么原理,为什么要这么写。
我认为cmp函数的一个特性就是,a是前面的元素, b是后面的元素,如果return 0, 那么sort就会将他们互换位置, return 1就会保持原来位置不变。
所以这函数可以解读为:
如果前面的元素比后面的元素大,就保持不变
如果前面的元素比后面的元素小,就交换他们的位置
简单来说就是把大的元素放在前面
我直接把这个cmp背下来不好吗?
cmp在结构体排序里也要用到,你背下来写那种二级排序的题也不会
在return里面 a放前面,b放前面,大于号,小于号,有好几种情况
理解起来有点绕怎么办?
我教你一种好理解的方法。
你永远把a放在前面,b放在后面。想把大的放在前面,就写大于号,想把小的放在前面,就写小于号(大口朝前大在前,小口朝前小在前)
这样就省去了分析的时间,还不容易出错。—— c++应用sort函数时的cmp函数怎么写

如果我们需要按照其他的排序准则,那么就需要我们自己定义一个bool类型的函数来传入。比如说我们按照每个数的个位进行从大到小排序,我们就可以根据自己的需求来写一个函数作为排序的准则传入到sort()中。
我们可以将这个函数定义为:

1
2
3
bool cmp(int x,int y){
return x % 10 > y % 10;
}

然后我们将这个cmp函数作为参数传入sort()中即可实现了上述排序需求。
完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int x,int y){
return x % 10 > y % 10;
}
int main(){
int num[10] = {65,59,96,13,21,80,72,33,44,99};
sort(num,num+10,cmp);
for(int i=0;i<10;i++){
cout<<num[i]<<" ";
}//输出结果:59 99 96 65 44 13 33 72 21 80
return 0;
}
对结构体进行排序

C++ sort()排序详解
例题9-6

[NOIP2007 普及组] 奖学金
题目描述
某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 55 名学生发奖学金。期末,每个学生都有 33 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学 排在前面,这样,每个学生的排序是唯一确定的。
任务:先根据输入的 33 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名名学生的学号和总分。注意,在前 55 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:
77 279279
55 279279
这两行数据的含义是:总分最高的两个同学的学号依次是 77 号、55 号。这两名同学的总分都是 279279 (总分等于输入的语文、数学、英语三科成绩之和) ,但学号为 77 的学生语文成绩更高一些。如果你的前两名的输出数据是:
55 279279
77 279279
则按输出错误处理,不能得分。
输入格式
n+1n+1行。
11 行为一个正整数n(300)n ( \le 300),表示该校参加评选的学生人数。
22n+1n+1 行,每行有 33 个用空格隔开的数字,每个数字都在 00100100 之间。第 jj 行的 33 个数字依次表示学号为 j1j-1 的学生的语文、数学、英语的成绩。每个学生的学号按照输入顺序编号为 1n1\sim n(恰好是输入数据的行号减 11)。
所给的数据都是正确的,不必检验。
输出格式
55 行,每行是两个用空格隔开的正整数,依次表示前 55 名学生的学号和总分。
样例 #1
样例输入 #1

1
2
3
4
5
6
7
8
9
10
11
12
13
8
80 89 89
88 98 78
90 67 80
87 66 91
78 89 91
88 99 77
67 89 64
78 89 98
```

样例输出 #2

8 265
2 264
6 264
1 258
5 258

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

```c++
#include <iostream>
#include <algorithm>
using namespace std;
typedef struct student
{
int id;
int score[3];
int total;
} STUDENT;
STUDENT a[310];
bool cmp(student x, student y)
{
if (x.total != y.total)
return x.total > y.total;
if (x.score[0] != y.score[0])
return x.score[0] > y.score[0];
return x.id < y.id;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
a[i].id = i;
cin >> a[i].score[0] >> a[i].score[1] >> a[i].score[2];
a[i].total = a[i].score[0] + a[i].score[1] + a[i].score[2];
}
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= 5; i++)
{
cout << a[i].id << " " << a[i].total << endl;
}
return 0;
}
```

##### 利用字符串进行排序
>P1781宇宙总统
题目描述
地球历公元 6036 年,全宇宙准备竞选一个最贤能的人当总统,共有 $n$ 个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统。
输入格式
第一行为一个整数 $n$,代表竞选总统的人数。
接下来有 $n$ 行,分别为第一个候选人到第 $n$ 个候选人的票数。
输出格式
共两行,第一行是一个整数 $m$,为当上总统的人的号数。
第二行是当上总统的人的选票。
样例 #1
样例输入 #1

5
98765
12365
87954
1022356
985678

1
2
3

样例输出 #1

4
1022356

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

提示
票数可能会很大,可能会到 $100$ 位数字。
$1 \leq n \leq 20$

```c++
#include <iostream>
#include <algorithm>
using namespace std;
struct node
{
int id;//编号
string num;//票数
} a[25];
bool cmp(node x, node y)
{
if(x.num.length()!=y.num.length())return x.num.length()>y.num.length();//a比b位数多时a在前面
return x.num>y.num;//位数相同,但a字典序排列比b大
//字符串的比较是比较字典序(第一位小的在前面,如果相同比较第二位,以此类推)例如10000小于1200小于200;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
a[i].id = i;
cin >> a[i].num;
}
sort(a + 1, a + n + 1, cmp);
cout << a[1].id << endl << a[1].num ;
return 0;
}

unique函数

unique() 去重函数

需要引入头文件#include< algorithm >

unique()函数是一个去重函数,STL中unique的函数 unique的功能是去除相邻的重复元素(只保留一个)
它并不真正把重复的元素删除,而是该函数把重复的元素移到后面去了,然后依然保存到了原数组中,然后返回去重后容器中不重复序列的最后一个元素的下一个元素。
因为unique去除的是相邻的重复元素,所以一般用之前都会要排一下序。

例9-5

[NOIP2006 普及组] 明明的随机数
题目描述
明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了 NN1110001000 之间的随机整数 (N100)(N\leq100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。
输入格式
输入有两行,第 11 行为 11 个正整数,表示所生成的随机数的个数 NN
22 行有 NN 个用空格隔开的正整数,为所产生的随机数。
输出格式
输出也是两行,第 11 行为 11 个正整数 MM,表示不相同的随机数的个数。
22 行为 MM 个用空格隔开的正整数,为从小到大排好序的不相同的随机数。
样例 #1
样例输入 #1

1
2
10
20 40 32 67 40 20 89 300 400 15

样例输出 #1

1
2
8
15 20 32 40 67 89 300 400

AC(运用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
#include<algorithm>
using namespace std;
int a[200];
int main(){
int n,cnt;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n);
cnt=unique(a,a+n)-a;//去重后最后一个元素对应的指针减去首地址的差就是去重后的元素个数
cout<<cnt<<endl;
for(int i=0;i<cnt;i++){
/*样例输入条件下,如果输出到n,结果为15 20 32 40 67 89 300 400 300 400 可以发现后面为重复的数字仍在数组当中*/
cout<<a[i]<<" ";
}
}
```

## 第15章线性表
### 数组
#### 例15.1
>深基15.1】询问学号
题目描述
有 $n(n \le 2 \times 10^6)$ 名同学陆陆续续进入教室。我们知道每名同学的学号(在 $1$ 到 $10^9$ 之间),按进教室的顺序给出。上课了,老师想知道第 $i$ 个进入教室的同学的学号是什么(最先进入教室的同学 $i=1$),询问次数不超过 $10^5$ 次。
输入格式
第一行 $2$ 个整数 $n$ 和 $m$,表示学生个数和询问次数。
第二行 $n$ 个整数,表示按顺序进入教室的学号。
第三行 $m$ 个整数,表示询问第几个进入教室的同学。
输出格式
输出 $m$ 个整数表示答案,用换行隔开。
样例 #1
样例输入 #1

10 3
1 9 2 60 8 17 11 4 5 14
1 5 9

1
样例输出 #1

1
8
5


##### AC代码  

```c++
#include <iostream>
#include <vector>
using namespace std;
int main()
{
  vector<int> stu;
  int n, m, temp;
  cin >> n >> m;
  for (int i = 0; i < n; i++)
  {
    cin >> temp;
    stu.push_back(temp);
    //cin >> stu[i];这种输入不行
  }
  for (int i = 0; i < m; i++)
  {
    cin >> temp;
    cout << stu[temp - 1] << endl;
  }
  return 0;
}

vector数组