第五章 数组¶
基础数学知识¶
- 因数、倍数与质数:理解谁能整除谁,知道 1 不是质数、2 是最小的质数。对应 2036、2040、1109。
- 完全平方数与因数配对:\(1,4,9,16,\ldots\) 是完全平方数;因数通常成对出现,完全平方数的平方根对应同一对中的两个相同因数。对应 2036 的规律解法。
- 用字母与下标表示一组数:读懂 \(a_i\) 表示第\(i\)项,\(a_{i,j}\) 表示第\(i\)行第\(j\)列;数学下标与 C++ 数组下标按各题约定对应。贯穿一维、二维数组题。
- 求和符号与对应项乘积:\(\sum_{i=1}^{n}x_i\) 就是把第 1 项到第\(n\)项依次相加;\(\sum a_i b_i\) 是对应位置相乘后再相加。对应 1104、1108、1125,先会展开,不用急着背符号。
- 百分数与比例:会用“部分数量除以总数量”求比例,再乘 \(100\%\) 写成百分数。对应 1106、1123、1131,注意各题要求输出比例、百分数值,还是带百分号的结果。
- 平均值、偏差与绝对值:会在去掉两个样本后重新计算平均值,并用 \(|x_i-\bar x|\) 表示与平均值的距离。对应 1114;绝对值不是把差直接原样输出。
- 余数与循环编号:理解编号走到末尾后回到开头,余数可以把结果限制在 \(0\) 到 \(n-1\)。对应 2037、1133、1136、1137。
- 坐标、闭区间与边界:理解 \((x,y)\) 的位置含义,以及 \(a\le x\le b\) 包含左右两个端点。对应 1107、1118;矩阵题的行列编号不等于平面坐标系的横纵轴方向。
- 矩阵及其基本运算:把矩阵理解为按行列排好的数表,学习对应位置相加、行列互换,以及“一行对一列”的乘法规则。对应 2041、2042、1119、1121~1128。
- 对角线的下标关系:理解同行列差不变、反向斜线行列和不变,例如 \(i-j=x-y\)、\(i+j=x+y\)。对应 2041、1120。
- 递推与杨辉三角:由前面已知的数计算后面的数,中间项满足 \(a_{i,j}=a_{i-1,j-1}+a_{i-1,j}\)。对应 2043,不需要先学组合数公式。
第一节 一维数组¶
2034:【例5.1】反序输出¶
提示 1:先记住读了几个数
输入4 8 2,倒着输出时应该先取哪个下标?
数组a已定义,从下标0存放;让n从0开始,每读到一个数就存入:
提示 2:从最后一个有效位置往回走
读完三个数时n是3,但最后一个下标是2。
用只有一个数的输入走一遍,检查循环会执行几次。
知识补充:读到输入结束
while (cin >> x) 会持续读取,直到输入结束或读取失败。
a[n++] = x 先把数放到下标 n,再让 n 加一。
回车只是换行,不代表输入结束。
输入完最后一个数,先按回车到新行。
Windows:按 Ctrl+Z,再按回车。
macOS:按 Control+D。
常见错误
别把第一个数当成个数,也别把零当结束标记。
读取失败时,不应再增加有效数据个数。
自测
输入:1 7 3 4 5 → 预期输出:5 4 3 7 1
输入:0 -2 9 → 预期输出:9 -2 0
自己试试:自己造一组数,先在纸上写出反序结果。
只在这组数的中间插入一个零,输出中应该多出什么?
参考代码
2035:【例5.2】平移数据¶
提示 1:先排输出顺序
把1 2 3 4左移一位,先写出结果。
这里数组从下标1存放,先输出下标2到n,最后该补谁?
提示 2:先不急着搬数组
接着补上原来第一个数,就能得到左移结果。
如果真的移动数组,先保存a[1],否则它会被覆盖。
常见错误
向左移动时,从前向后补位,避免覆盖还没搬走的数据。
只有一个数时,结果不变。
自测
输入:4 1 2 3 4 → 预期输出:2 3 4 1
输入:1 9 → 预期输出:9
自己试试:自己造一组互不相同的数,先写出左移一位的结果。
只把第一个数换掉,输出中的哪个位置应该改变?
方法一:调整输出顺序
方法二:实际移动数组
2036:【例5.3】开关门¶
提示 1:跟着第三位服务员走
只画6扇门,第三位服务员要操作哪几扇?
门号是3、6,下一扇总比前一扇多3。
提示 2:把一次反转写出来
全局bool door[]已定义,先用false对应关门。
把它放进for (int j = i; j <= n; j += i),就模拟了第i位服务员。
外面还需要哪一层循环,才能让所有服务员依次操作?
知识补充:状态与初始化
全局 bool 数组默认是 false,这里让它对应初始的“关”。
! 会把真假反过来,也就对应一次开关操作。
知识补充:完全平方数规律(拓展)
第\(k\)扇门的操作次数,等于\(k\)的正因数个数。
因数通常成对出现,偶数次反转后仍然关着。
完全平方数的平方根只算一个因数,因此总数为奇数。
例如 9 的因数是 1、3、9,反转三次后打开。
常见错误
这里是切换状态,不是每次都开门。
两层循环中,一个表示服务员,一个表示门。
自测
输入:10 → 预期输出:1 4 9
输入:1 → 预期输出:1
方法一:模拟开关
方法二:完全平方数规律(拓展)
2037:【例5.4】约瑟夫问题¶
提示 1:把圆圈变成队伍
先用4个人、报到3出局手算一轮。
没出局的人挪到队尾,队头换人,这样能不能接着绕圈?
提示 2:每次只处理队头一人
队列q中已按顺序放好1到n,报数计数k从0开始。
取走队头不等于让他出局:没报到m,还要q.push(t)。
提示 3:出局后从哪里继续
报到m时输出t,并把k清零,不再放回队列。
把两个分支补进去,再用m = 1检查是否人人依次出局。
知识补充:队列的操作
queue<int> q 定义整数队列。
q.push(x) 放入队尾,q.front() 查看队头,q.pop() 移除队头。
q.empty() 判断是否为空;非空时才能取队头。
常见错误
出局的人不能重新入队。
输出人的编号,不是报数;m 为 1 时也要能结束。
自测
输入:8 5 → 预期输出:5 2 8 7 1 4 6 3
输入:4 1 → 预期输出:1 2 3 4
方法一:队列模拟
方法二:数组标记
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
bool vis[N];
int n, m;
int cnt;
vector<int> ans;
int main() {
cin >> n >> m;
int i = 0;
while (cnt < n) {
int num = 0;
while (num < m - 1) {
while (vis[i]) i = (i + 1) % n;
num++;
i = (i + 1) % n;
}
while (vis[i]) i = (i + 1) % n;
ans.push_back(i + 1);
vis[i] = true;
cnt++;
i = (i + 1) % n;
}
for (auto x : ans) cout << x << ' ';
return 0;
}
方法三:数组删除与补位
方法四:双向链表(拓展)
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int l[N], r[N];
void remove(int k) {
l[r[k]] = l[k];
r[l[k]] = r[k];
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
r[i] = (i + 1) % n;
l[i] = (i - 1 + n) % n;
}
int k = 0;
for (int i = 0; i < n; i++) {
for (int j = 1; j < m; j++) k = r[k];
printf("%d ", k + 1);
remove(k);
k = r[k]; //下一个人
}
return 0;
}
2038:【例5.5】最大数位置¶
提示 1:留下数,也留下位置
对3 9 9 2,题目要输出2还是3?
先让第一个数当擂主,位置记作1。
常见错误
> 保留第一个最大值,>= 会保留最后一个。
不要用零初始化最大值,否则全负数时可能出错。
自测
输入:5 3 9 9 2 1 → 预期输出:2
输入:3 -8 -2 -5 → 预期输出:2
自己试试:自己造一组数,让最大值只出现在最前面。
在最后再放一个相同的最大值,输出的位置应不应该变?
参考代码
2039:【例5.6】冒泡排序¶
提示 2:末尾排好后少比较一次
数组从下标0存放,第一轮比较到j = n - 2。
第i轮前,末尾已有i个数排好:
外层该做几轮?用两个数的数组检查一下。
常见错误
访问 a[j + 1] 时,要保证这个位置仍在本轮范围内。
每个结果单独输出一行。
自测
输入:
预期输出:
输入:
预期输出:
自己试试:自己造一组数,先写出从大到小的顺序。
只打乱输入顺序,排序后的结果应不应该变?
参考代码
#include <bits/stdc++.h>
using namespace std;
int a[1010];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j + 1 < n - i; j++) {
if (a[j] < a[j + 1]) swap(a[j], a[j + 1]);
}
}
for (int i = 0; i < n; i++) cout << a[i] << '\n';
return 0;
}
2040:【例5.7】筛选法找质数¶
提示 1:先把不可能的数划掉
在2到20中圈出2,再划掉它大于自身的倍数。
下一个没被划掉的是谁?重复这个动作。
提示 2:标记的是合数
全局bool st[]默认全为false,表示暂未筛掉。
确认i未被标记后,可以这样筛:
先用i = 3列出会标记的数;为什么不能从j = i开始?
知识补充:筛的起点
模板从\(2i\)开始,标记质数\(i\)大于自身的倍数,保留\(i\)本身。
以后还可以把起点改成\(i^2\),因为\(2i, 3i, \ldots, (i-1)i\)都含有小于\(i\)的质因数,前面已经筛过。
线性筛列为拓展,先掌握埃氏筛即可。
常见错误
1 不是质数。
线性筛遍历质数时,下标不能超过已找到的质数个数。
方法一:埃氏筛法
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int primes[N], cnt;
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
int main() {
int n;
cin >> n;
get_primes(n);
for (int i = 0; i < cnt; i++) cout << primes[i] << '\n';
return 0;
}
方法二:线性筛法(拓展)
// 线性筛法
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 510;
int primes[M], idx, n;
bool st[N];
int main() {
cin >> n;
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[idx++] = i;
for (int j = 0; j < idx && primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
for (int i = 0; i < idx; i++) cout << primes[i] << '\n';
return 0;
}
1102:与指定数字相同的数的个数¶
提示 1:目标值最后才读到
输入顺序是个数、这一组数、目标值。
读数组时还不知道要找谁,所以先存下这组数。
常见错误
数组容量不等于有效数据个数。
多遍历未输入的位置,可能把默认的零也算进去。
自测
输入:5 0 2 0 3 0 0 → 预期输出:3
输入:3 1 2 3 9 → 预期输出:0
方法一:下标遍历
方法二:vector 与范围循环(拓展)
1103:陶陶摘苹果¶
提示 1:刚好够到也能摘
假设身高100,站上30厘米的凳子后,130和131哪个能摘到?
常见错误
别漏掉等号。
输入先是十个苹果的高度,再是身高,没有额外的个数。
自测
输入:100 130 131 140 150 160 170 180 190 200 100 → 预期输出:2
参考代码
1104:计算书费¶
提示 1:先核对一种书
只买第一种书两本,其余全不买,费用应该是多少?
让单价数组price的顺序和输入顺序一致。
常见错误
单价顺序不能错,零本也要读入。
用 %.1f,保证整数金额也显示一位小数。
自测
输入:1 0 0 0 0 0 0 0 0 0 → 预期输出:28.9
输入:0 0 0 0 0 0 0 0 0 0 → 预期输出:0.0
参考代码
1105:数组逆序重存放¶
提示 1:这次要改数组本身
把1 2 3 4 5写在纸上,先交换最左和最右。
接着该交换哪两个位置?中间的3需要动吗?
知识补充:reverse 的范围
reverse(a, a + n) 反转下标0到n - 1的元素。
右端 a + n 不包含在处理范围里。
常见错误
左右下标要同时移动。
只倒着输出,没有完成这题要练的“重存放”。
自测
输入:5 1 2 3 4 5 → 预期输出:5 4 3 2 1
输入:1 7 → 预期输出:7
方法一:首尾交换
方法二:reverse 函数
1106:年龄与疾病¶
提示 1:把分界点分清
先给18、19、35、36、60、61各找一个年龄组。
四组人数加起来,应该等于什么?
提示 2:先计数,再算百分比
让a统计第一组人数,初值为0:
自己补另外三组;算第一组比例时用1.0 * a / n * 100,为什么要乘1.0?
常见错误
区间不能漏人,也不能重复计数。
除法先转浮点数,printf 中的百分号写 %%。
参考代码
// 输出0-18, 19-35, 36-60, 61以上,年龄的比例
#include <bits/stdc++.h>
using namespace std;
int a, b, c, d;
int n;
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
// 判断这个人的年龄,属于哪个分类的
if (x >= 0 && x <= 18) a++;
if (x >= 19 && x <= 35) b++;
if (x >= 36 && x <= 60) c++;
if (x >= 61) d++;
}
printf("%.2lf%%\n", 1.0 * a / n * 100);
printf("%.2lf%%\n", 1.0 * b / n * 100);
printf("%.2lf%%\n", 1.0 * c / n * 100);
printf("%.2lf%%", 1.0 * d / n * 100);
return 0;
}
1107:校门外的树¶
提示 1:重叠不能重复减
画出0到6的位置,把区间[1,3]和[3,5]涂掉。
位置3虽然被涂两次,树能移走两次吗?
提示 2:涂色对应数组标记
全局tree[]初值为0,对应有树;移走后记为1。
处理完所有区间,再数0到L中仍为0的位置,别漏掉位置0。
常见错误
总共 \(L+1\) 个位置,区间两端也算。
直接相加区间长度,会重复扣掉重叠部分。
自测
输入:10 2 2 4 4 6 → 预期输出:6
输入:5 1 0 5 → 预期输出:0
自己试试:自己画一条短路,安排两个互不重叠的施工区间。
只移动其中一个区间,让它们重叠,剩下的树会怎样变化?
方法一:数组标记
#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int tree[N]; //tree[i] = 0代表有树
int L, M;
int cnt;
int main() {
cin >> L >> M;
while (M--) {
int l, r;
cin >> l >> r;
for (int i = l; i <= r; i++) tree[i] = 1;
}
for (int i = 0; i <= L; i++)
if (tree[i] == 0) cnt++;
cout << cnt;
return 0;
}
方法二:map 标记(拓展)
1108:向量点积计算¶
提示 1:上下对齐再乘
对1 2 3和4 5 6,先写出三次乘法。
第二个数应该和另一组的哪个数相乘?
提示 2:每一对加进总和
两组数已读入a、b,sum用long long并从0开始。
自己补遍历;1LL让乘法也按long long计算,不只是最后存入时才转换。
常见错误
不是所有数两两相乘,也不是分别求和再相乘。
乘法前用 1LL 提升类型,别等溢出后再存入 long long。
自测
输入:3 1 2 3 4 5 6 → 预期输出:32
输入:2 -1 2 3 -4 → 预期输出:-11
参考代码
1109:开关灯¶
提示 1:先和开关门对照
这次灯开始全亮,最后要找灭的灯。
全局bool light[]默认是false,这里人为约定它对应亮。
提示 2:跟着一个人反转
第i个人只碰编号为i的倍数的灯:
外层让i从1走到m,结束后哪个状态表示灭?
提示 3:逗号放在两项之间
已经找到一盏灭灯时,后面再找到的灭灯前才加逗号。
用flag记录是否输出过编号,先设为false。
把这段放在“第j盏灯灭”的分支里。
常见错误
处理的是前 m 个人,不是 n 个人。
编号之间用逗号分隔,末尾不加逗号。
自测
输入:10 3 → 预期输出:1,5,6,7
输入:1 1 → 预期输出:1
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
bool light[N]; // 全局数组默认是 false,这里约定 false 对应亮、true 对应灭
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
for (int j = i; j <= n; j += i)
light[j] = !light[j];
bool flag = false;
for (int j = 1; j <= n; j++)
if (light[j]) {
if (flag) cout << ',';
cout << j;
flag = true;
}
return 0;
}
1110:查找特定的值¶
提示 1:两个相同目标要选谁
在8 3 3 9中找3,先写出题目要求的位置。
数组从下标1存放,找不到先让pos = -1。
常见错误
题目位置和数组下标要对应好。
没找到要输出 -1,别让后面的重复值覆盖第一次的位置。
自测
输入:4 8 3 3 9 3 → 预期输出:2
输入:3 1 2 3 7 → 预期输出:-1
自己试试:自己造一组数,让目标值只出现在最后。
只把它移到最前面,输出的位置应该怎样变化?
方法一:记录位置后输出
方法二:找到后直接结束
1111:不高兴的津津¶
提示 1:先判断有没有不高兴
一天两项时间为4和4,恰好8小时,要记录这一天吗?
再比较总时长为9的两天,题目要求选哪一天?
提示 2:符合条件才打擂台
按周一到周日读入x、y;最高时长maxn = -1,答案ret = 0。
如果七天都不超过8小时,ret会保持多少?
常见错误
刚好 8 小时不算超过。
并列时不能用 >= 覆盖,否则会留下后一天。
自测
输入:5 3 4 4 6 3 5 4 4 4 3 4 2 2 → 预期输出:3
输入:4 4 4 4 4 4 4 4 4 4 4 4 4 4 → 预期输出:0
参考代码
1112:最大值和最小值的差¶
提示 1:先把两个擂主放到两端
对-5 -2 -8,最大值和最小值分别是多少?
每个数的绝对值不超过10000,先让maxn足够小、minn足够大:
这样第一个数一定能更新两份纪录,后面继续比较。
常见错误
最小值初始化为零,会影响全正数的结果。
最大值也别从0开始,全是负数时,0并不是读入的数。
自测
输入:4 -5 2 9 -1 → 预期输出:14
输入:1 7 → 预期输出:0
方法一:用极小值、极大值初始化
本题最大差不超过20000,读入、比较和求差都用int即可。
方法二:用第一个数初始化
先自己读一遍:第一个数在哪里读入,为什么循环只读n - 1次?
用n = 1走一下,看看不进循环时为什么也能得到正确答案。
1113:不与最大数相同的数字之和¶
提示 1:先分清删的是哪些数
对2 5 5 1,两个5都不参与求和,结果是多少?
提示 2:分两遍,别边猜边加
先把数存入a并找出maxn,再让long long sum = 0;。
把这句放进第二遍循环;全部数相同,为什么会得到0?
常见错误
不是从总和里只减掉一个最大值。
全部相同时,答案为零。
自测
输入:5 2 5 5 1 3 → 预期输出:6
输入:3 4 4 4 → 预期输出:0
自己试试:自己造一组数,先算出不与最大值相同的数字之和。
只在末尾再加一个相同的最大值,答案应不应该变?
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N];
int n;
int maxn;
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
if (i == 0 || a[i] > maxn) maxn = a[i];
}
long long sum = 0;
for (int i = 0; i < n; i++)
if (a[i] != maxn) sum += a[i];
cout << sum;
return 0;
}
1114:白细胞计数¶
提示 1:先划掉,再算
拿6 2 11 3 3这五次结果试一下。
划掉一次最大值11、一次最小值2,剩下6 3 3。
先求这三个数的平均数,再看它们各自离平均数有多远。
提示 2:数组不搬动,记住跳过哪两格
沿方法一写,数组下标从0开始:刚才的11在a[2],2在a[1]。
用hi记最大值的位置,lo记最小值的位置,注意存的是下标。
初始hi = lo = 0,遍历到a[i]时:
循环前先设double sum = 0;,遍历时把a[i]加进去。
提示 3:平均数只算留下来的
刚才五个数的总和是25,减掉11和2,剩下12,一共留下3个数。
对应代码:
这里ave是平均数,n - 2是留下的次数;数组里的数不用真的删掉。
提示 4:偏差就是离平均数多远
剩下6 3 3,平均数为4,距离分别是2、1、1,所以最大偏差是2。
abs(a[i] - ave)取差的绝对值,3减4得到-1,取绝对值后距离是1。
maxn从0开始,只让留下的数参加比较:
11已经被划掉了,别再拿它和平均数比。
知识补充:所有数都一样怎么办
例如6 6 6 6,没有谁更大、更小,hi、lo会一直都是0。
但题目仍要去掉两次结果,所以代码用if (hi == lo) lo = 1;,让两个位置不同。
这句在计算平均数之前执行;只减掉两次6,其余的6仍要保留。
题目保证n > 2,所以另选下标1有位置可用,n - 2也不会是0。
常见错误
重复的最大、最小值,不能全部删掉。
全部相同时,也要删两个不同位置,分母是 \(n-2\)。
自测
输入:5 1 2 3 4 5 → 预期输出:3.00 1.00
输入:4 6 6 6 6 → 预期输出:6.00 0.00
输入:5 6 2 11 3 3 → 预期输出:4.00 2.00
输入:3 1 2 3 → 预期输出:2.00 0.00
输入:5 10.1 10.1 10.2 10.5 10.8 → 预期输出:10.27 0.23
方法一:记录被删样本下标
先读入数组,再求总和、找两个位置,最后只遍历留下的数求最大偏差。
#include <bits/stdc++.h>
using namespace std;
double a[310];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int hi = 0, lo = 0;
double sum = 0;
for (int i = 0; i < n; i++) {
sum += a[i];
if (a[i] > a[hi]) hi = i;
if (a[i] < a[lo]) lo = i;
}
// 全部相同时,也要删除两个不同的样本
if (hi == lo) lo = 1;
double ave = (sum - a[hi] - a[lo]) / (n - 2);
double maxn = 0;
for (int i = 0; i < n; i++)
if (i != hi && i != lo) maxn = max(maxn, abs(a[i] - ave));
printf("%.2lf %.2lf", ave, maxn);
return 0;
}
方法二:排序后处理中间样本
从小到大排序后,最小值在a[0],最大值在a[n - 1]。
后面两遍循环都只处理下标1到n - 2,分别求平均数、最大偏差。
#include <bits/stdc++.h>
using namespace std;
double a[310];
int n;
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
sort(a, a + n);
double sum = 0.0;
for (int i = 1; i <= n - 2; i++) sum += a[i];
double ave = sum / (n - 2);
double maxn = 0;
for (int i = 1; i <= n - 2; i++)
maxn = max(maxn, fabs(a[i] - ave));
printf("%.2lf %.2lf", ave, maxn);
return 0;
}
1115:直方图¶
提示 1:给每个数准备一个计数格
输入0 2 2,0、1、2分别出现几次?
没有出现的1也要有一行输出。
常见错误
计数数组的下标是数值,不是输入顺序。
未出现的数也要输出零。
参考代码
1116:最长平台¶
提示 1:沿着平台走
给1 1 2 2 2 3划段,最长的是哪一段?
用last记前一个数,cnt记当前长度,maxn记见过的最长长度。
常见错误
不是统计一个值在全串中的总次数。
最后一段也要参与最大值比较。
自测
输入:8 1 1 2 2 2 3 3 3 → 预期输出:3
输入:4 -1 -1 -1 -1 → 预期输出:4
自己试试:自己造一个非降序序列,让最长平台出现在最后。
只在末尾再添一个相同的数,答案应该增加多少?
方法一:维护连续长度
方法二:按段扫描
// 先找到这一段的末尾,再跳到下一段
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N], n;
int maxn;
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) {
int j = i;
while (j < n && a[j] == a[i]) j++;
maxn = max(maxn, j - i);
i = j - 1;
}
cout << maxn;
return 0;
}
1117:整数去重¶
提示 1:第一次见才留下
按顺序看30 10 30 20,第二次遇到30时需要再输出吗?
提示 2:输出后做个记号
全局bool st[]默认是false,表示还没见过这个值。
每读到一个数就执行这段,为什么能保留第一次出现的顺序?
常见错误
先排序会改变原顺序。
数值当下标时,先确认范围放得进标记数组。
自测
输入:7 30 10 30 20 10 20 40 → 预期输出:30 10 20 40
自己试试:自己造一组数,让同一个数出现两次,中间隔着别的数。
只把后一次出现的这个数移到末尾,去重结果应不应该变?
参考代码
1118:铺地毯¶
提示 1:从最上面那张找起
两张地毯都盖住目标点,后铺的在上还是先铺的在上?
按铺设顺序存好后,试着从编号n往回找。
提示 2:先只检查横坐标
第i张左下角为(a[i], b[i]),宽高为g[i]、k[i]。
这是横向覆盖条件;仿照它写纵向条件,再用&&连接。
第一次覆盖就记录编号并停止,始终没找到则输出-1。
常见错误
边界也算覆盖,找不到输出 -1。
覆盖条件是 \(a\le x\le a+g\) 且 \(b\le y\le b+k\)。
自测
输入:2 0 0 3 3 1 1 2 2 2 2 → 预期输出:2
输入:1 0 0 1 1 2 2 → 预期输出:-1
参考代码
#include <bits/stdc++.h>
using namespace std;
int a[10010], b[10010], g[10010], k[10010];
int main() {
int n, x, y;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> g[i] >> k[i];
cin >> x >> y;
int ans = -1;
for (int i = n; i >= 1; i--) {
if (a[i] <= x && x <= a[i] + g[i] && b[i] <= y && y <= b[i] + k[i]) {
ans = i;
break;
}
}
cout << ans;
return 0;
}
第二节 二维数组¶
2041:【例5.9】新矩阵¶
提示 1:在格子上写下行列号
画三阶矩阵,两条对角线上的格子分别满足什么关系?
这里行列都从1编号,副对角线上两编号的和是多少?
常见错误
两个独立的加法,会让中心格加两次。
用 || 合并条件。
自测
输入:
预期输出:
自己试试:自己造一个奇数阶矩阵,先标出中心格。
只把中心格的数增加 \(1\),输出中的这个数应该增加多少?
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int a[N][N], n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
if (i == j || (i + j) == (1 + n)) a[i][j] += 10;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
2042:【例5.10】稀疏矩阵¶
提示 1:先按读入顺序找非零数
用一个两行三列的矩阵,把每个非零数的行号、列号写在旁边。
只输出这些位置,需要保存已经处理过的零吗?
提示 2:读到一个就决定是否输出
外层循环行i、内层循环列j,都从1开始,当前读入值为x:
补上读入与两层循环,顺序是否正好和题目要求一致?
常见错误
行、列都从 1 编号,别写反。
非零不等于大于零,负数也要输出。
参考代码
2043:【例5.11】杨辉三角形¶
提示 1:先从上一行找来源
写下1 2 1,下一行中间的3来自哪两个数?
第i行只有i个数,两端先填1。
提示 2:中间格子用上一行
数组行列从1开始,对1 < j < i的位置:
先填完上一行再填下一行;输出每行时,列循环该到n还是i?
常见错误
第 i 行只输出 i 项。
两端先处理,别把数组里多开的格子当成三角形的一部分。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int a[N][N];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
if (j == 1 || j == i) a[i][j] = 1;
else a[i][j] = a[i-1][j] + a[i-1][j-1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
2044:【例5.12】回文字串¶
提示 1:结束句点不参加比较
abba.中的点用来结束输入,去掉它后再找首尾。
已用getline读入s,可以先写s.pop_back();删掉题目给定的末尾句点。
提示 2:从两端各走一步
设l = 0、r = (int)s.size() - 1。
把它放在while (l < r)里;停下时,怎样分辨是遇到不同字符,还是已经比较完?
常见错误
这题输出 Yes、No,与 1146 的大小写不同。
不要把结束句点参与比较。
自测
输入:abba. → 预期输出:Yes
输入:abc. → 预期输出:No
方法一:双下标比较
方法二:反转后比较
2045:【例5.13】蛇形填数¶
提示 2:这一行代码,拆开看
i是行号,j是列号,下标从1开始;往下走,行号加一,列号不变。
全局数组a默认全为0,这里用0表示空格;idx从0开始,记录已经填了几个数。
从i = 1, j = n出发,向下填数的这一行:
先别急,这里可以拆成下面几步来理解:
++idx先加一,再拿新编号填数;i++用当前行号填好这一格,再移到下一行。
循环条件同时检查:没走出下边界,而且这一格还空着。
提示 3:为什么要退一步,再转弯
看三阶方阵,向下填完1、2、3后,i = 4, j = 3,已经走过头了。
接着执行:
这句就是先执行i--;,再执行j--;。
先退回第3行,再向左到第2列,下一个数4就填在a[3][2]。
向左时,列号越来越小,这一段可以接着写:
你跟着走一下:填完4、5后,停在第几行、第几列?
再照这个办法,补向上、向右的两段。
提示 4:第二圈,靠什么停下来
第一圈填完8后,下一格是a[2][2],填上9。
再往下,a[3][2]已经有4了,!a[i][j]不成立,这一段就停下,不会覆盖原来的数。
所以每段既要判断边界,也要判断这一格有没有填过。
外层用while (idx < n * n)控制,只要还没填满,就继续按四个方向走。
常见错误
一阶矩阵也要填上 1。
四个方向的边界不同:向下看i <= n,向左看j >= 1,另两方向自己对照。
一段结束后,要先退回最后填的格子,再向新方向走一步。
方法一:四个方向逐段填数
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int a[N][N], n;
int idx;
int main() {
cin >> n;
int i = 1, j = n;
while (idx < n * n) {
while (i <= n && !a[i][j]) a[i++][j] = ++idx;
i--, j--;
while (j >= 1 && !a[i][j]) a[i][j--] = ++idx;
j++, i--;
while (i >= 1 && !a[i][j]) a[i--][j] = ++idx;
i++, j++;
while (j <= n && !a[i][j]) a[i][j++] = ++idx;
j--;
i++;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
方法二:方向数组模拟
这个方法每次只填一格,用d记录方向:0向下、1向左、2向上、3向右。
dr[d]是行号的变化量,dc[d]是列号的变化量,例如向下就是行号加1、列号不变。
nr、nc先试探下一格,不能走时,用d = (d + 1) % 4换到下一个方向,再重新算下一格。
#include <bits/stdc++.h>
using namespace std;
int a[25][25];
int dr[] = {1, 0, -1, 0}, dc[] = {0, -1, 0, 1};
int main() {
int n;
cin >> n;
int row = 1, col = n, d = 0;
for (int idx = 1; idx <= n * n; idx++) {
a[row][col] = idx;
if (idx == n * n) break;
int nr = row + dr[d], nc = col + dc[d];
if (nr < 1 || nr > n || nc < 1 || nc > n || a[nr][nc] != 0) {
d = (d + 1) % 4;
nr = row + dr[d];
nc = col + dc[d];
}
row = nr;
col = nc;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
1119:矩阵交换行¶
提示 1:列不动,只换两行
把两行上下对齐,同一列的两个数应该互换。
这里固定读入5行5列,待交换行号存入n、m。
常见错误
别额外读矩阵规模。
交换同一行时,结果应不变。
自测
输入:
预期输出:
参考代码
#include <bits/stdc++.h>
using namespace std;
int a[10][10], n, m;
int main() {
for (int i = 1; i <= 5; i++)
for (int j = 1; j <= 5; j++)
cin >> a[i][j];
cin >> n >> m;
for (int j = 1; j <= 5; j++)
swap(a[m][j], a[n][j]);
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++)
cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
1120:同行列对角线的格¶
提示 1:先分开画四条线
在方格纸上标出(x, y),先画同行和同列。
同行只改变列号,同列只改变行号,不必枚举所有格子。
提示 2:沿斜线看编号关系
行列从1开始,向右下走一步,行列都加1,差会不会变?
另一条斜线用行列和判断;按题目要求,最后一条从下往上输出,行循环应从哪里开始?
常见错误
最后一条斜线按左下到右上输出,行号递减。
找的是经过给定格子的线,不只是方阵自身的两条对角线。
参考代码
#include <bits/stdc++.h>
using namespace std;
int n, x, y;
int main() {
cin >> n >> x >> y;
for (int j = 1; j <= n; j++) printf("(%d,%d) ", x, j);
cout << '\n';
for (int i = 1; i <= n; i++) printf("(%d,%d) ", i, y);
cout << '\n';
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i - j == x - y) printf("(%d,%d) ", i, j);
cout << '\n';
for (int i = n; i >= 1; i--)
for (int j = 1; j <= n; j++)
if (i + j == x + y) printf("(%d,%d) ", i, j);
return 0;
}
1121:计算矩阵边缘元素之和¶
提示 1:别把角重复加两次
给三行三列矩阵描边,四个角各只能加一次。
只有一行时,所有格子是不是都在边缘?
提示 2:每个格子只判断一次
行号i从1到n,列号j从1到m,当前数为x,sum初值为0。
这一个if和四个分别累加的if,在角点处有什么不同?
常见错误
一行或一列的矩阵,每个元素仍只算一次。
别把四条边分别相加后,又忘了处理重复位置。
自测
输入:
预期输出:40
输入:
预期输出:6
自己试试:自己造一个三行三列的矩阵,先算出边缘元素之和。
只改变中心格,答案应不应该变?
参考代码
1122:计算鞍点¶
提示 1:一个格子要过两关
先挑一格,看它是否为本行最大;再看它是否为本列最小。
只通过其中一关,能称为鞍点吗?
提示 2:先给每行每列做记录
数组从1编号,让row[i]记第i行最大值,col[j]记第j列最小值。
从各行、各列第一个元素初始化,再分别向后比较。
这是更新行最大值的一步,列最小值该怎样改?
提示 3:用两份记录检查交点
把条件放入遍历,找到时输出位置和值,并做标记;全部查完还没找到时,再输出not found。
常见错误
行最大、列最小,别记反。
没有鞍点时输出 not found。
参考代码
#include <bits/stdc++.h>
using namespace std;
int a[6][6], row[6], col[6];
int main() {
for (int i = 1; i <= 5; i++)
for (int j = 1; j <= 5; j++) cin >> a[i][j];
for (int i = 1; i <= 5; i++) {
row[i] = a[i][1];
for (int j = 2; j <= 5; j++) row[i] = max(row[i], a[i][j]);
}
for (int j = 1; j <= 5; j++) {
col[j] = a[1][j];
for (int i = 2; i <= 5; i++) col[j] = min(col[j], a[i][j]);
}
bool found = false;
for (int i = 1; i <= 5 && !found; i++) {
for (int j = 1; j <= 5; j++) {
if (a[i][j] == row[i] && a[i][j] == col[j]) {
cout << i << ' ' << j << ' ' << a[i][j];
found = true;
break;
}
}
}
if (!found) cout << "not found";
return 0;
}
1123:图像相似度¶
提示 1:相同也要在同一位置
两张图都有一个5,但一个在左上、一个在右下,能算对应位置相同吗?
提示 2:只对齐同一个格子比较
第一张存入a,读第二张的第i行第j列数值x时:
cnt从0开始;总格数是n * m,用1.0 * cnt / (n * m) * 100计算百分数,再按题目格式输出。
常见错误
总格数是 \(nm\),不是 n 或 m。
用浮点除法,保留两位小数,不加百分号。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
int cnt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int x;
cin >> x;
if (x == a[i][j]) cnt++;
}
double res = cnt * 1.0 / (n * m);
printf("%.2lf", res * 100);
return 0;
}
1124:矩阵加法¶
提示 1:先算左上角一格
两张矩阵上下对齐,左上角的结果需要用到别的行或列吗?
常见错误
不是把整张表求成一个总和。
行数、列数在循环中别混用。
参考代码
#include <bits/stdc++.h>
using namespace std;
int a[110][110];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int x;
cin >> x;
a[i][j] += x;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
1125:矩阵乘法¶
提示 1:先不写三层循环
先算一格:一行1 2 3,配上一列4 5 6,得到\(1\times4+2\times5+3\times6\)。
算出这个和,再看代码里三个数是怎样配对的。
结果的一格来自第一张的一行、第二张的一列,不是只乘同位置。
提示 2:固定结果的一格
第一张是\(n\times k\)矩阵,第二张是\(k\times m\)矩阵,相乘得到\(n\times m\)矩阵。
这里把共同的长度记作k,输入的三个数依次读到n、k、m。
固定第i行第j列,沿共同长度k累加:
c是全局int数组,初值为0;为什么一个下标固定i,另一个固定j?
提示 3:再把这一格扩成整张表
外面两层循环分别枚举结果的行i、列j,边界是n和m。
先用两行三列乘三行四列试一遍,结果应该有几行几列?
常见错误
不是对应位置直接相乘。
累加前结果应为0;本题用全局int数组c,默认已初始化为0。
自测
输入:
预期输出:
自己试试:自己造两个能相乘的小矩阵,先手算结果中的一格。
只改第一个矩阵中的一个数,结果中哪些格子可能改变?
参考代码
共同长度k小于100,每个元素绝对值不超过1000,一格累加结果的绝对值小于\(10^8\),int足够。
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N], b[N][N], c[N][N];
int n, k, m;
int main() {
cin >> n >> k >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= k; j++) cin >> a[i][j];
for (int i = 1; i <= k; i++)
for (int j = 1; j <= m; j++) cin >> b[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int p = 1; p <= k; p++)
c[i][j] += a[i][p] * b[p][j];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) cout << c[i][j] << ' ';
cout << '\n';
}
return 0;
}
1126:矩阵转置¶
提示 1:把第一列横过来
给两行三列矩阵填上1到6,转置后第一行是哪两个数?
一共要输出几行?
提示 2:先固定列,再走行
读入数组a后,固定原矩阵列号j:
在外面补上j从1到m的循环;一列读完后换行,别在每个数后换行。
常见错误
转置不等于旋转。
拿非方阵检查,更容易看出方向是否正确。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= n; i++)
cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
1127:图像旋转¶
提示 1:先看四个角去了哪里
画一个两行三列的表,顺时针转90度。
原来的左下角会成为新图的哪个角?
提示 2:每列从下往上读
第j列变成结果的一行:
外层从左到右枚举j,每列结束换行;和转置那题相比,哪一层循环反过来了?
常见错误
结果从 \(n\times m\) 变成 \(m\times n\)。
别照搬转置时从上到下的方向。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int j = 1; j <= m; j++) {
for (int i = n; i >= 1; i--)
cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
1128:图像模糊处理¶
提示 1:先只算一个内部像素
在三行三列的图里,中心格需要读取哪五个旧值?
如果刚算完就改原图,下一个格子读到的还是旧值吗?
提示 3:用一行图检查边界
若图只有一行,还有内部格子吗?
把边缘判断放在前面,再进入计算分支,能避免访问不存在的上下邻居。
常见错误
除法写 / 5.0,再用 round 四舍五入。
不能边算边覆盖原图,也不能只平均四个邻居。
自测
输入:
预期输出:
输入:
预期输出:10 20 30
自己试试:自己造一张像素值全部相同的合法图像,先预测模糊结果。
只改变一个内部像素,哪些位置的结果可能改变?
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N], b[N][N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (i == 1 || i == n || j == 1 || j == m) b[i][j] = a[i][j];
else b[i][j] = round((a[i][j] + a[i-1][j] + a[i+1][j] + a[i][j-1] + a[i][j+1]) / 5.0);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) cout << b[i][j] << ' ';
cout << '\n';
}
return 0;
}
第三节 字符类型和字符数组¶
2046:【例5.15】替换字母¶
提示 1:整句话先保留下来
句子里有空格,cin >> s能读完整句吗?
先用getline(cin, s)读句子,再读要替换的字符a和新字符b。
常见错误
有空格的文本用 getline,别只读到第一个词。
每个位置只替换一次。
参考代码
2047:【例5.16】过滤空格¶
提示 1:三个空格只留第一个
在纸上写a b,把第二、第三个空格划掉。
决定当前空格要不要输出时,只看前一个原字符够不够?
提示 2:别在开头访问前一个位置
已读入整行s,从下标0遍历:
其余字符照常输出;用行首两个空格试一遍,会留下几个?
常见错误
逐词读取会丢掉原行首尾的空格。
不要把“压成一个”做成“全部删除”。
自测
输入:a b c → 预期输出:a b c
输入: a b → 预期输出: a b
第二组输入和输出都有行首、行尾空格,复制时留意。
自己试试:自己写一句带连续空格的话,先写出过滤后的结果。
只把其中一段连续空格加长,输出应不应该变?
方法一:比较相邻字符
方法二:记录上一字符状态
preSpace表示前一个原字符是不是空格,开头还没有前一个字符,所以初值为false。
for (char c : s)依次取出字符串里的字符,每处理一个都要更新preSpace。
2048:【例5.18】串排序¶
提示 1:字典序不是按长短排
先排b、ab、a、aa,从左往右比较第一个不同字符。
a是ab的前缀,这时谁在前?
知识补充:字典序与 sort
从左往右看,第一个不同的字符决定顺序。
sort(s, s + n) 默认按这个顺序排列前n个字符串。
常见错误
不忽略大小写,也不按长度排序。
相同字符串仍要保留原来的份数。
参考代码
2049:【例5.19】字符串判等¶
提示 1:先看处理后剩下什么
A b C和aB c直接比较,当然不一样。
但这道题不区分大小写,还要忽略空格,可以先把两行都整理一下:
整理后都是abc,所以输出YES。
字符顺序不能变,ab和ba仍然不同,不要排序。
提示 2:先读完整的两行
cin >> s1遇到空格就停,第一行的A b C只能读到A。
这里用getline,一次读一整行:
s1、s2保存原来的两行,a、b是两个空字符串,用来存整理后的内容。
提示 3:一个字符一个字符地整理
先只处理s1,每次取一个字符,暂时放在c里:
遇到空格,continue就跳过本轮,空格不会加进a。
遇到大写字母,先转成小写;其他字符保持原样,再用a += c接到新串末尾。
拿A b C跟一下:读到A,a变成a;读到空格,仍是a;读到b,变成ab。
最后一个C处理完,a应该是什么?
提示 4:第二行,你来处理
仿照上一段循环,遍历s2,把整理后的字符接到b末尾。
最后用a == b比较两个完整字符串,相同输出YES,否则输出NO。
只比较长度够不够?拿abc和abd试一下。
知识补充:为什么加上这个差值
'a' - 'A'是大小写字母编码的差值,其他英文字母也有同样的差值。
所以c += 'a' - 'A';能把大写字母转成对应的小写字母。
前面的判断不能省:已经是小写的b,不需要再加一次。
for (char c : s1)中的c是取出的字符副本,修改它不会改掉原串s1,整理结果要接到a中。
常见错误
第二段循环要遍历s2,并写b += c,别复制代码后忘了改名字。
空格要跳过,不能让它进入新串;大小写转换只处理大写字母。
最后比较整理后的a、b,结果输出大写YES、NO。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
string s1, s2, a, b;
getline(cin, s1);
getline(cin, s2);
for (char c : s1) {
if (c == ' ') continue;
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
a += c;
}
for (char c : s2) {
if (c == ' ') continue;
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
b += c;
}
if (a == b) cout << "YES";
else cout << "NO";
return 0;
}
2050:【例5.20】字串包含¶
提示 1:让跨首尾的部分连起来
把AABCD写两遍:AABCDAABCD。
原本绕过末尾的CDAA,现在能在纸上连续圈出来吗?
提示 2:先确定谁是长串
读入a、b,让a保存较长的串:
在t中查找b,用find与string::npos判断;为什么两份就够,见下面的知识补充。
知识补充:为什么拼接两份就够了
成环以后,走过末尾,就会回到开头。
把原串再接一份,第二份的开头,就接在第一份的末尾后面。
例如 AABCD 拼成 AABCDAABCD,跨过首尾的 CDAA 就连续出现了。
设长串长度为 \(n\),短串长度不超过 \(n\)。
从第一份的任意位置出发,最多再读 \(n\) 个字符,两份的长度就足够了。
所以,环上跨过首尾的匹配,可以变成拼接串里的普通子串匹配。
常见错误
循环移动不等于任意打乱字符。
普通查找失败,不代表跨过首尾也找不到。
自测
输入:AABCD CDAA → 预期输出:true
输入:abc d → 预期输出:false
自己试试:自己选一个长串,从末尾取几个字符,再接上开头的几个字符,组成短串。
只把长串循环移动一位,包含关系应不应该变?
参考代码
1839:【05NOIP提高组】谁拿了最多奖学金¶
提示 1:先只给一个人算钱
一个人同时满足两条条件,奖金是二选一,还是都能拿?
先让个人奖金now = 0,不要急着找全班最高。
提示 2:五项奖励分别判断
期末均分存入pingjun、论文数存入lunwen,其中一项是:
照题面补其余四项,用独立的if;平均分恰好80能拿这一项吗?
提示 3:个人算完再比较
maxn初值为-1,top记录姓名;当前姓名为s:
另用sum累加每个人的奖金;同奖金额时为什么不写>=?
常见错误
不能用 else if 把奖项变成只能选一种。
最高奖金并列时,保留先输入的人。
参考代码
#include <bits/stdc++.h>
using namespace std;
int n;
string top;
int maxn = -1;
int sum;
int main() {
cin >> n;
while (n--) {
string s;
int pingjun, pingyi;
char ganbu, xibu;
int lunwen;
cin >> s >> pingjun >> pingyi >> ganbu >> xibu >> lunwen;
int now = 0;
if (pingjun > 80 && lunwen >= 1) now += 8000;
if (pingjun > 85 && pingyi > 80) now += 4000;
if (pingjun > 90) now += 2000;
if (pingjun > 85 && xibu == 'Y') now += 1000;
if (pingyi > 80 && ganbu == 'Y') now += 850;
if (now > maxn) {
maxn = now;
top = s;
}
sum += now;
}
cout << top << '\n' << maxn << '\n' << sum;
return 0;
}
1129:统计数字字符个数¶
提示 1:数的是字符,不是整数
在A12 3!里,把数字字符逐个圈出,一共几个?
整行读入s,计数cnt从0开始。
常见错误
字符界限要带单引号。
读整行,别漏掉空格后面的数字。
自测
输入:A0 b12! 9 → 预期输出:4
输入:abc ! → 预期输出:0
参考代码
1130:找第一个只出现一次的字符¶
提示 1:先别见到一次就输出
读abac到第二个字符时,能确定b以后不会再出现吗?
先统计完整个串,再决定答案。
提示 2:把字母放进26个计数格
全局cnt[]初值为0,小写字母用s[i] - 'a'变成0到25:
第二遍按原字符串顺序找计数为1的字符,为什么不能按字母表顺序找?
提示 3:找到第一个就结束
输出答案后可以直接return 0;。
只有整串找完仍没找到,才输出no;想想它应放在循环内还是外。
常见错误
不能边统计边认定只出现一次,后面可能还有。
没有符合项时输出 no。
自测
输入:abac → 预期输出:b
输入:aabb → 预期输出:no
自己试试:自己造一个字符串,让第一个字符只出现一次。
只在末尾再添一个相同字符,原来的答案还能保留吗?
方法一:先计数再按原序查找
#include <bits/stdc++.h>
using namespace std;
const int N = 30, M = 1e5 + 10;
int cnt[N]; //0...25
char s[M];
int main() {
scanf("%s", s);
int len = strlen(s);
for (int i = 0; i < len; i++)
cnt[s[i] - 'a']++;
for (int i = 0; i < len; i++)
if (cnt[s[i] - 'a'] == 1) {
cout << s[i];
return 0;
}
cout << "no";
return 0;
}
方法二:双层循环比较(拓展)
只用于短串练习,最坏需要\(O(n^2)\)次比较,不作为长串的提交版本。
例如前半段全是a、后半段全是b,检查后半段时会反复扫过前面的a。
方法一只扫描两遍,长串也应优先用它。
1131:基因相关性¶
提示 1:先算匹配比例
AC和AT对应位置相同几个,比例是多少?
阈值恰好为0.5时,应输出什么?
提示 2:逐位计数再比较
两串存入s1、s2,长度为len,cnt从0开始:
补循环后用1.0 * cnt / len >= limit判断,其中limit表示阈值,也就是匹配比例需要达到的下限。
常见错误
第一个输入是小数阈值,limit要定义成double,不能用int。
例如输入0.8,cin >> limit若读入的是int,只会读走0,留下.8,后面的字符串也会读错。
用浮点除法,不乘 100。
比较对应位置,不是每种字母的总数量。
自测
输入:0.5 AC AT → 预期输出:yes
输入:0.8 AC AT → 预期输出:no
参考代码
1132:石头剪子布¶
提示 1:先划掉平局
把玩家一可能出的三种手势写成三行,每行写出它能赢谁。
平局先判断,剩下只需列玩家一获胜的三种组合。
提示 3:方法二,把手势排成一个圈
给石头Rock编号0,剪刀Scissors编号1,布Paper编号2。
0赢1,1赢2,2又赢0,正好绕一圈。
如果玩家一的编号是x,它能赢的编号就是(x + 1) % 3。
代入0、1算一下,再试试2:(2 + 1) % 3是不是回到了0?
提示 4:先把字符串换成编号
方法二中,仍用a、b读入手势,下面先转换玩家一的手势:
玩家二的编号存入y,你来补同样的转换。
先判断x == y是平局,再用(x + 1) % 3 == y判断玩家一获胜,剩下就是玩家二获胜。
常见错误
单词和输出的大小写按题目写。
每轮独立判断,不能沿用上一轮状态。
方法二的编号顺序要和公式对应,不能换了编号,还照搬原来的判断。
方法一:直接判断胜负组合
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string a, b;
cin >> a >> b;
string ret;
if (a == b) ret = "Tie";
else if ((a == "Rock" && b == "Scissors") ||
(a == "Scissors" && b == "Paper") ||
(a == "Paper" && b == "Rock")) ret = "Player1";
else ret = "Player2";
cout << ret << '\n';
}
return 0;
}
方法二:整数编码与循环关系
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string a, b;
cin >> a >> b;
int x, y;
if (a == "Rock") x = 0;
else if (a == "Scissors") x = 1;
else x = 2;
if (b == "Rock") y = 0;
else if (b == "Scissors") y = 1;
else y = 2;
string ret;
if (x == y) ret = "Tie";
else if ((x + 1) % 3 == y) ret = "Player1";
else ret = "Player2";
cout << ret << '\n';
}
return 0;
}
方法三:按首字母分类判断
#include <bits/stdc++.h>
using namespace std;
string s1, s2;
int main() {
int n;
cin >> n;
while (n--) {
int a = 0, b = 0;
cin >> s1 >> s2;
if (s1[0] == 'R') {
if (s2[0] == 'S') a++;
else if (s2[0] == 'P') b++;
}
if (s1[0] == 'P') {
if (s2[0] == 'R') a++;
else if (s2[0] == 'S') b++;
}
if (s1[0] == 'S') {
if (s2[0] == 'P') a++;
else if (s2[0] == 'R') b++;
}
if (a == b) cout << "Tie";
if (a > b) cout << "Player1";
if (a < b) cout << "Player2";
cout << '\n';
}
return 0;
}
1133:输出亲朋字符串¶
提示 1:先确定最后一位的邻居
对012,依次配对0和1、1和2,最后2该和谁配?
提示 2:旧字符别提前改掉
先用getline(cin, s)读完整行,长度记为len,另用空串ans存结果:
自己算一下最后一个下标(i + 1) % len;如果直接改s[i],最后一对还取得到原来的首字符吗?
常见错误
直接输出编码和,会显示整数。
空格也参与编码,不能用cin >> s只读一个单词。
相加得到的字符不一定是字母,不要自行修改结果。
自测
输入:012 → 预期输出:acb
输入:11 → 预期输出:bb
输入:0 1 → 预期输出:PQa
自己试试:自己造一个两位字符串,先算出两个输出字符。
只交换输入中的两个字符,输出应不应该变?
参考代码
1134:合法C标识符查¶
提示 1:先把首位单独看
a1和1a都只含字母、数字,为什么只有一个合法?
写下首位允许的字符,再给后续位置补上数字。
提示 2:一个非法字符就够判错
读入整行s,逐位取char c = s[i],分别判断letter是否英文字母、digit是否数字。
这是当前位置合法的条件;若不满足,就把合法标记改为false,后面的字符能把它改回来吗?
常见错误
空格也不合法,要检查整行。
本题只按题面给出的字符规则检查,不另加关键字判断。
自测
输入:_a1 → 预期输出:yes
输入:1abc → 预期输出:no
输入:ab cd → 预期输出:no
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
getline(cin, s);
bool valid = !s.empty();
for (int i = 0; i < (int)s.size(); i++) {
char c = s[i];
bool letter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
bool digit = (c >= '0' && c <= '9');
if (!(letter || c == '_' || (i > 0 && digit))) valid = false;
}
if (valid) cout << "yes";
else cout << "no";
return 0;
}
1135:配对碱基链¶
提示 1:先写配对表
A配T,C配G,反过来各配什么?
拿ATCG逐位写出结果。
提示 2:一位只能替换一次
自己补C、G两个分支;如果用两个独立的if,刚改成的T会不会又被改回去?
常见错误
用互斥的分支。
独立 if 连续修改,可能把刚换好的字符又换回去。
自测
输入:ATCG → 预期输出:TAGC
输入:AAAA → 预期输出:TTTT
参考代码
1136:密码翻译¶
提示 1:先留出两个特殊字母
a后移一位是b,z后移应该回到哪里?
把Z也写出来,再决定普通字母的处理范围。
提示 2:普通字母和末尾分开处理
再补普通大小写字母的后移分支,空格、数字、标点保持原样。
常见错误
只改字母,其他字符不动。
不能让 z、Z 直接加一变成标点。
自测
输入:aZ z9! → 预期输出:bA a9!
方法一:处理普通字母与边界字母
#include <bits/stdc++.h>
using namespace std;
string s;
int main() {
getline(cin, s);
for (int i = 0, len = s.size(); i < len; i++) {
if ((s[i] >= 'a' && s[i] <= 'y') || (s[i] >= 'A' && s[i] <= 'Y'))
s[i]++;
else if (s[i] == 'z') s[i] = 'a';
else if (s[i] == 'Z') s[i] = 'A';
}
cout << s;
return 0;
}
方法二:字母编号取余
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
getline(cin, s);
for (int i = 0, len = (int)s.size(); i < len; i++) {
if (s[i] >= 'a' && s[i] <= 'z') s[i] = 'a' + ((s[i] - 'a') + 1) % 26;
if (s[i] >= 'A' && s[i] <= 'Z') s[i] = 'A' + ((s[i] - 'A') + 1) % 26;
}
cout << s;
return 0;
}
1137:加密的病历单¶
提示 1:拆成三次手算
用aZ依次做后移三位、反转、大小写互换,先写出最终结果。
不要一次心算三件事,先核对每一步。
提示 3:字符处理和整串处理分开
每个字符还要互换大小写,整串最后调用reverse(s.begin(), s.end());。
逐字符变换可以先做,反转可以后做;用刚才的aZ检查是否与手算一致。
常见错误
大小写各自在自己的范围内循环。
反转的是整个串,不是单独一组字母。
自测
输入:abcXYZ → 预期输出:cbaFED
参考代码
#include <bits/stdc++.h>
using namespace std;
string s;
int main() {
cin >> s;
int len = s.size();
for (int i = 0; i < len; i++) {
char c = s[i];
if (c >= 'a' && c <= 'z') {
s[i] = 'a' + (c - 'a' + 3) % 26;
s[i] -= 'a' - 'A';
}
else {
s[i] = 'A' + (c - 'A' + 3) % 26;
s[i] += 'a' - 'A';
}
}
reverse(s.begin(), s.end());
cout << s;
return 0;
}
1138:将字符串中的小写字母转换成大写字母¶
提示 1:只圈出要改变的字符
对a B z9!,先圈小写字母,其余字符都要保留。
常见错误
不能对所有字符统一减编码差。
空格、数字和标点应保持原样。
自测
输入:a B z9! → 预期输出:A B Z9!
参考代码
1139:整理药名¶
提示 1:首位和其余位置分开
把iBUPROFEN整理成首字母大写、其余小写。
首位已经大写时,需要再改变吗?
提示 2:第二段循环从哪里开始
单个药名已读入s,先把s[0]中的小写转大写。
后续位置只改大写字母:
这段循环要从下标1开始;在外面再套读取n个药名的循环。
常见错误
只有一个字母也要处理。
已经符合要求的字母,不必再加减编码差。
参考代码
1140:验证子串¶
提示 1:包含要求连续
ac的字母都在abc里,它是abc的子串吗?
再试两串完全相同的情况,也算包含。
提示 2:先只判断一个方向
对已读入的两个字符串s、s2:
它表示在s里找到s2;这个方向没找到,还要不要试另一个方向?
输出时分清谁是子串、谁包含它,别把两者写反。
知识补充:find 的返回值
a.find(b) 查找b在a中第一次出现的位置。
找不到时返回 string::npos,因此用 != string::npos 判断找到。
常见错误
只匹配到长串末尾、不够短串全长,不算成功。
两个串相等也算包含。
自测
输入:abc bc → 预期输出:bc is substring of abc
输入:abc bcd → 预期输出:No substring
输入:abc abc → 预期输出:abc is substring of abc
方法一:find 函数
#include <bits/stdc++.h>
using namespace std;
int main() {
string s, s2;
cin >> s >> s2;
if (s2.size() < s.size()) {
if(s.find(s2) != string::npos) cout << s2 << ' ' << "is substring of" << ' ' << s;
else cout << "No substring";
}
else {
if(s2.find(s) != string::npos) cout << s << ' ' << "is substring of" << ' ' << s2;
else cout << "No substring";
}
return 0;
}
方法二:逐个起点手动匹配
#include <bits/stdc++.h>
using namespace std;
int main() {
string a, b;
cin >> a >> b;
if (a.size() < b.size()) swap(a, b);
int n = (int)a.size(), m = (int)b.size();
bool found = false;
for (int i = 0; i + m <= n; i++) {
int j = 0;
while (j < m && a[i + j] == b[j]) j++;
if (j == m) {
found = true;
break;
}
}
if (found) cout << b << " is substring of " << a;
else cout << "No substring";
return 0;
}
1141:删除单词后缀¶
提示 1:只检查单词末尾
flower的er在末尾,error的er在开头,哪一个能删?
若单词只有一个字母,访问倒数第三位会怎样?
提示 2:先保护长度,再比较
长度记为len,检查三字母后缀ing:
匹配后用s.erase(len - 3);;仿照它补er和ly,只删除一次。
知识补充:compare 与 erase
s.compare(pos, count, text) 比较指定片段与 text,相等时返回 0。
s.erase(pos) 删除从 pos 开始到末尾的部分。
常见错误
先检查长度,再访问倒数位置。
删除后别再用旧长度继续访问,也别重复删除。
自测
输入:singing → 预期输出:sing
输入:fly → 预期输出:f
输入:a → 预期输出:a
输入:flower → 预期输出:flow
自己试试:自己选一个带指定后缀的单词,先写出删除结果。
只在单词末尾再添一个字母,让它不再带指定后缀,还应该删除吗?
方法一:下标比较后缀
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
int len = (int)s.size();
if (len >= 3 && s[len - 3] == 'i' && s[len - 2] == 'n' && s[len - 1] == 'g') {
s.erase(len - 3);
} else if (len >= 2 && ((s[len - 2] == 'e' && s[len - 1] == 'r') ||
(s[len - 2] == 'l' && s[len - 1] == 'y'))) {
s.erase(len - 2);
}
cout << s;
return 0;
}
方法二:compare 函数
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
int len = (int)s.size();
if (len >= 3 && s.compare(len - 3, 3, "ing") == 0) s.erase(len - 3);
else if (len >= 2 && (s.compare(len - 2, 2, "er") == 0 ||
s.compare(len - 2, 2, "ly") == 0)) s.erase(len - 2);
cout << s;
return 0;
}
1142:单词的长度¶
提示 1:先跨过空格再数单词
给I love Cpp划出三个单词,连续空格要不要算空单词?
提示 2:用两个下标夹住一词
整行s长为len,i指向未处理位置,先写:
若已到末尾就停止;否则令j = i,让j走到空格或末尾。
常见错误
跳过空格时也要检查下标。
逗号只放在两项之间,首尾都不多加。
自测
输入: I love Cpp → 预期输出:1,4,3
输入:word → 预期输出:4
方法一:整行扫描
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
getline(cin, s);
int len = (int)s.size(), i = 0;
bool first = true;
while (i < len) {
while (i < len && s[i] == ' ') i++;
if (i == len) break;
int j = i;
while (j < len && s[j] != ' ') j++;
if (!first) cout << ',';
cout << j - i;
first = false;
i = j;
}
return 0;
}
方法二:逐词读取
cin >> s会跳过连续空白,每次读到一个单词。
终端里回车不代表输入结束,结束输入的操作见本章2034。
1143:最长最短单词¶
提示 1:先圈出完整单词
对aa, bbb c,dddd,空格和逗号都能分开单词。
最长、最短各是哪一个?同长度时保留先出现的。
提示 2:从起点走到分隔处
整行长为len,i已经指向单词首字符,先令j = i。
下次从j接着处理,遇到分隔符先跳过。
提示 3:两场擂台分开打
longest、shortest初始为空,取出word后:
仿照它更新最长词;相同长度不更新,才能留下先出现的词。
常见错误
逗号不属于单词。
相同长度若继续更新,会留下后出现的词。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
string s, longest, shortest;
getline(cin, s);
int len = (int)s.size();
for (int i = 0; i < len; ) {
if (s[i] == ' ' || s[i] == ',') {
i++;
continue;
}
int j = i;
while (j < len && s[j] != ' ' && s[j] != ',') j++;
string word = s.substr(i, j - i);
if (longest.empty() || word.size() > longest.size()) longest = word;
if (shortest.empty() || word.size() < shortest.size()) shortest = word;
i = j;
}
cout << longest << '\n' << shortest;
return 0;
}
1144:单词翻转¶
提示 1:空格留在原地
把I love Cpp中的单词各自倒过来,两个连续空格应该还在吗?
提示 2:遇到单词才收集
整行s长为len,i指向一个非空格字符;令j = i,空串temp接住这个词:
输出temp;遇到空格则直接原样输出,不放入单词。
提示 3:不要重复处理同一词
收集后j指向空格或末尾;若外层for还有i++,这里应先写i = j - 1;。
用没有尾部空格的word走一遍,检查最后一个词能否输出。
常见错误
别反转整句话,也别把连续空格重新拼成一个。
末尾没有空格时,也要处理最后一个单词。
自测
输入:I love Cpp → 预期输出:I evol ppC
输入: word → 预期输出: drow
自己试试:自己写一句带连续空格的话,最后一个单词后不留空格。
只在句末补一个空格,最后一个单词的翻转结果应不应该变?
参考代码
#include <bits/stdc++.h>
using namespace std;
string s;
int main() {
getline(cin, s);
for (int i = 0, len = s.size(); i < len; i++) {
if (s[i] == ' ') {
cout << s[i];
continue;
}
int j = i;
string temp;
while (j < len && s[j] != ' ') temp += s[j++];
reverse(temp.begin(), temp.end());
cout << temp;
i = j - 1;
}
return 0;
}
1145:字符串p型编码¶
提示 1:先在纸上分组
先把111221中连续相同的字符圈成一组。
三个1编码成31,你能接着写出后面两组的编码吗?
最后一个1能和开头的三个1合并吗?
提示 2:这一段,要记住什么
下面沿着last、cnt的写法试一遍。
读入非空字符串s后,先把第一个字符算进当前段:
last记这一段的字符,cnt记这一段已经有几个字符。
接下来从下标1往后看,遇到相同字符时:
用111221手算到第三个1,此时last和cnt各是多少?
提示 3:读到不同字符,先做什么
接着读到2,上一段结束了,可以输出:
如果先把last改成'2',这一句会把上一段错编码成什么?
输出后,新的一段从眼前这个字符开始:
把这些语句放进else,自己接着处理剩下的字符。
提示 4:最后一段,谁来提醒它结束
走到111221末尾时,最后一段后面还有不同字符吗?
再单独试一下输入7:循环一次也不执行,还应该输出17。
想想这句该放在循环里面,还是循环结束后:
关上提示,把初始化、循环和最后一次输出连起来写一遍。
常见错误
换段时先输出旧段,再修改last,新段的cnt从1开始。
如果少了最后一段,先用7检查循环结束后的输出。
寻找段尾的写法,要先判断j < len再访问s[j]。
自测
输入:1112231 → 预期输出:31221311
输入:7 → 预期输出:17
输入:111 → 预期输出:31
输入:1121 → 预期输出:211211
自己试试:自己造一串相同的数字,先写出编码。
只把中间一个数字换成别的数字,会分成几段?
方法一:寻找每段的末尾
方法二:用last和cnt记录连续段
last记录当前这一段的字符,cnt记录已经连续出现了多少次。
遇到不同字符,先输出上一段,再开始新的一段。
循环结束后,别漏掉最后一段。
1146:判断字符串是否为回文¶
提示 1:先找一对不同的字符
对abca,第一对首尾相同,能立刻判断是回文吗?
这题没有末尾句点,不要删字符。
提示 2:一对通过才继续往里走
非空字符串读入s,设l = 0、r = (int)s.size() - 1。
放进while (l < r);退出后若l < r,说明在哪一步停住了?
常见错误
一个字符也是回文。
不要删掉最后一位正文,输出小写 yes、no。
自测
输入:abba → 预期输出:yes
输入:abca → 预期输出:no
输入:a → 预期输出:yes
方法一:双下标比较
方法二:反转后比较
1147:最高分数的学生姓名¶
提示 1:姓名跟着分数走
先看80 Li、95 Wang,第二个人分数更高,要改哪两项记录?
提示 2:相同最高分先别换人
成绩非负,先设maxn = -1;每次读入分数x和姓名s:
补上读入循环,最后只输出姓名;再添一名95分学生,谁应该留下?
常见错误
输入是分数在前、姓名在后。
用 >= 会覆盖并列时先输入的学生。
自测
输入:3 90 Amy 95 Bob 95 Cara → 预期输出:Bob
参考代码
1148:连续出现的字符¶
提示 1:一段够长就符合
对abbbcc,设k = 3,先圈出符合条件的连续段。
若这一段有4个b,是否也包含连续3个b?
提示 2:先量出这一段的长度
i指向一段开头,len是字符串长度,令j = i:
当cnt >= k时输出s[i]并停止;没有达到就跳过整段,别重复从段中间数。
提示 3:别在中途宣布找不到
用标记记录是否找到,所有段都检查完后,才决定要不要输出No。
用k = 1试一下,第一段应该怎样处理?
常见错误
末尾一段也要检查。
处理完一段要跳过,k 为 1 时第一个字符就符合。
自测
输入:3 abbbcc → 预期输出:b
输入:4 aaabb → 预期输出:No
输入:1 abc → 预期输出:a
自己试试:自己造一个字符串,让某段字符恰好连续出现 \(k\) 次。
只把这一段中的一个字符换掉,它还一定能满足要求吗?
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int k;
string s;
cin >> k >> s;
bool flag = false;
for (int i = 0, len = (int)s.size(); i < len; i++) {
int j = i;
while (j < len && s[j] == s[i]) j++;
int cnt = j - i;
if (cnt >= k) {
flag = true;
cout << s[i];
break;
}
i = j - 1;
}
if (!flag) cout << "No";
return 0;
}
1149:最长单词2¶
提示 1:末尾句点不是单词
对I love coding.,先把三个单词圈出,句点不要圈进去。
两个最长词一样长时,要留下先出现的。
提示 2:找到边界再取出单词
i指向单词开头,j从i向后走,遇到空格、句点或字符串末尾停下。
处理完令i = j,下一轮跳过分隔符。
常见错误
只删句末的点,别把每个单词的末位都删掉。
最后一个单词也要参与比较。
自测
输入:I love coding. → 预期输出:coding
输入:aa bb c. → 预期输出:aa
方法一:扫描单词边界
#include <bits/stdc++.h>
using namespace std;
int main() {
string s, ans;
getline(cin, s);
int len = (int)s.size();
for (int i = 0; i < len; ) {
if (s[i] == ' ' || s[i] == '.') {
i++;
continue;
}
int j = i;
while (j < len && s[j] != ' ' && s[j] != '.') j++;
string word = s.substr(i, j - i);
if (word.size() > ans.size()) ans = word;
i = j;
}
cout << ans;
return 0;
}
方法二:逐词读取
方法三:逐字符累积
#include <bits/stdc++.h>
using namespace std;
string ret, t;
int main() {
string s;
getline(cin, s);
for (int i = 0, len = s.size(); i < len; i++) {
if (s[i] != ' ' && s[i] != '.') t += s[i];
else {
if (t.size() > ret.size()) ret = t;
t.clear();
}
}
if (t.size() > ret.size()) ret = t;
cout << ret;
return 0;
}