91.乒乓球
题目描述
给你一系列的比赛数据(WL形式),分别按照11分制和21分制的比赛规则进行统计,然后输出统计结果。
代码
cpp
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
// 处理并输出单组测试数据的结果
void process_test_case(const string& s, bool is_first) {
// 除了第一组数据,后续每组数据前需要加一个空行分隔
if (!is_first) {
cout << "\n";
}
// --- 11 分制统计 ---
int w = 0, l = 0, games_played = 0;
for (char c : s) {
if (c == 'W') w++;
else if (c == 'L') l++;
// 获胜条件:某方达到 11 分及以上,且分差大于等于 2
if ((w >= 11 || l >= 11) && abs(w - l) >= 2) {
cout << w << ":" << l << "\n";
w = 0; l = 0; // 比分清零
games_played++; // 记录已经打完的完整局数
}
}
if (w > 0 || l > 0 || games_played == 0) {
cout << w << ":" << l << "\n";
}
// 11 分制和 21 分制之间需要一个空行
cout << "\n";
// --- 21 分制统计 ---
w = 0; l = 0; games_played = 0;
for (char c : s) {
if (c == 'W') w++;
else if (c == 'L') l++;
// 获胜条件:某方达到 21 分及以上,且分差大于等于 2
if ((w >= 21 || l >= 21) && abs(w - l) >= 2) {
cout << w << ":" << l << "\n";
w = 0; l = 0;
games_played++;
}
}
// 同理,处理 21 分制的末尾残局
if (w > 0 || l > 0 || games_played == 0) {
cout << w << ":" << l << "\n";
}
}
int main() {
string line;
string current_s = "";
bool first_case = true;
// 逐行读取输入
while (getline(cin, line)) {
for (char c : line) {
if (c == 'E') {
// 遇到 E,说明这组数据读取完毕,开始计算并输出
process_test_case(current_s, first_case);
first_case = false;
current_s = ""; // 清空记录,为下一组数据做准备
break; // 跳出内部 for 循环,直接忽略本行 E 后面的所有杂乱字符
} else if (c == 'W' || c == 'L') {
// 持续累加 W 和 L
current_s += c;
}
}
}
return 0;
}
总结
核心思路 :本题重点在于状态模拟与字符串处理。需要按字符逐个读取,分别累加胜负分数;当一方达到目标分数(11或21)且分差大于等于2时,立刻结算并清零。此外,还要精准处理多组数据中 E 字符的截断以及严格的空行换行格式。
错误教训 :最大的坑在于边界情况处理 。在上一局完美结束且遇到 E 时多输出了一个 0:0,这是因为忽略了当前 OJ 的特殊要求。
排错心得:核对测试用例时,不要被网页界面的"滚动条"或局部视错觉误导。面对长输出报错,务必将自己代码的完整输出与标准输出进行首尾、逐行对比。
92.字符串统计
明明最近在做一个有关字符串的统计工作。两个由小写字母组成的字符串s1和s2,明明需要统计出以下四种关系:
(1)在s1或s2中存在的字母(包括在s1和s2中都存在的字母);
(2)在s1中且在s2中的字母;
(3)在s1中但不在s2中的字母,在s2中但不在s1中的字母;
(4)不在s1中且也不在s2中的字母;
例如两个字符串s1为"lkjsvoahs",s2为"qglhskjdfg":
(1)在s1或者在s2或者s1、s2中都存在的字母:adfghjkloqsv;
(2)在s1中且在s2中的字母:hjkls;
(3)在s1中但不在s2中的字母,在s2中但不在s1中的字母:adfgoqv;
(4)不在s1中且也不在s2中的字母:bceimnprtuwxyz;
明明统计了很久,但是由于统计过程十分繁琐,且很容易出错,导致明明的进度非常慢,很有可能因为统计不完而错过了晚上的约会。因此明明想请你帮个忙,帮他写一个程序,用程序来统计出以上几项内容。
明明的问题可以归结为:
输入两串由小写字母组成的字符串s1和s2,比较其中的字母,输出以下四项,输出的字母以字典顺序排列:
(1)在s1或s2中存在的字母(包括在s1和s2中都存在的字母);
(2)在s1中且在s2中的字母;
(3)在s1中但不在s2中的字母,在s2中但不在s1中的字母;
(4)不在s1中且也不在s2中的字母;
例如字符串s1为sadf,s2为asdf,则需输出以下四行(注意输出的格式):
in s1 or s2:adfs
in s1 and s2:adfs
in s1 but not in s2 ,or in s2 but not in s1:
not in s1 and s2:bceghijklmnopqrtuvwxyz
代码
cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string s1, s2;
bool isFirstTestCase = true;
while (cin >> s1 >> s2) {
// 控制空行格式:除了第一组,后面的每组输出前都加一个空行
if (!isFirstTestCase) {
cout << "\n";
}
isFirstTestCase = false;
// 使用长度为 26 的布尔数组记录字母是否出现
vector<bool> in1(26, false);
vector<bool> in2(26, false);
for (char c : s1) {
in1[c - 'a'] = true;
}
for (char c : s2) {
in2[c - 'a'] = true;
}
string res1 = "", res2 = "", res3 = "", res4 = "";
// 遍历 a 到 z,天然按字典序处理
for (int i = 0; i < 26; ++i) {
char c = 'a' + i;
bool b1 = in1[i];
bool b2 = in2[i];
if (b1 || b2) res1 += c; // 在 s1 或 s2 中
if (b1 && b2) res2 += c; // 在 s1 且 s2 中
if (b1 != b2) res3 += c; // 在 s1 不在 s2,或在 s2 不在 s1
if (!b1 && !b2) res4 += c; // 都不在
}
cout << "in s1 or s2:" << res1 << "\n";
cout << "in s1 and s2:" << res2 << "\n";
cout << "in s1 but not in s2 ,or in s2 but not in s1:" << res3 << "\n";
cout << "not in s1 and s2:" << res4 << "\n";
}
return 0;
}
总结
题目的核心要求是统计小写字母的出现情况,并且要求输出的字母按字典顺序排列 。因为小写英文字母一共只有 26 个,所以可以直接利用两个长度为 26 的布尔数组来分别映射并记录 s1 和 s2 中各个字母是否出现过。
这种"哈希映射"的方法有几个好处:
- 自动排序:当索引 从0 到 25(对应字母 'a' 到 'z')遍历时,天然就是字典序,省去了后续排序的步骤。
- 逻辑清晰 :四种情况可以直接用布尔逻辑表示:
- 并集 (情况1):
in1[i] || in2[i] - 交集 (情况2):
in1[i] && in2[i] - 对称差集 (情况3):
in1[i] != in2[i](即异或操作,只有一个为真) - 补集 (情况4):
!in1[i] && !in2[i]
- 并集 (情况1):
93.隐藏口令
题目描述
有时候程序员有很奇怪的方法来隐藏他们的口令。
Billy"Hacker"Geits会选择一个字符串S(由L个小写字母组成,5<=L<=100,000),然后他把S顺时针绕成一个圈。
如字符串cbadfa,绕成一个圈后,我们认为字符串首尾相连。
每次取其中一个字母作为起始字母,并顺时针依次取字母而组成一个字符串。这样将得到一些字符串。
比如字符串cbadfa,按照以上规则取出的字符串有:
cbadfa badfac adfacb dfacba facbad acbadf
我们找到最小的那个字符串,可知为acbadf,也可知道它的第一个字符'a'在原字符串cbadfa中为第6个字符(位置从1开始),
将得到的结果6减1得到5,这就是我们需要的口令。
再比如字符串alabala,绕成一个圈后,每次取其中一个字母作为起始字母,并顺时针依次取字母而组成一个字符串。这样将得到一些字符串:
alabala labalaa abalaal balaala alaalab laalaba aalabal
我们找到最小的那个字符串,可知为aalabal,它的第一个字母'a'在原字符串中位置为7,7-1=6,则6为口令。
注:如果按照规则有两个字符串都是最小的,则取前面那一个。
代码
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int getMinRepresentation(const string& s) {
int n = s.length();
int i = 0, j = 1, k = 0;
while (i < n && j < n && k < n) {
// 取模运算实现首尾相连的环形效果
int char_i = s[(i + k) % n];
int char_j = s[(j + k) % n];
if (char_i == char_j) {
k++;
} else {
if (char_i > char_j) {
// i 开头的这段被淘汰
i = i + k + 1;
} else {
// j 开头的这段被淘汰
j = j + k + 1;
}
// 保证 i 和 j 错开
if (i == j) {
j++;
}
// 每次淘汰后,匹配长度 k 清零重新开始
k = 0;
}
}
// 返回更小的那个下标作为起点
return min(i, j);
}
int main() {
int L;
if (!(cin >> L)) return 0;
string s = "";
string temp;
while (s.length() < L && cin >> temp) {
s += temp;
}
int ans = getMinRepresentation(s);
cout << ans << "\n";
return 0;
}
总结
具体思路如下:
- 维护两个指针
i和j(代表两个可能成为最小表示起点的候选位置),初始时i = 0, j = 1。再维护一个匹配长度k = 0。 - 比较以
i和j为起点,第k个字符的大小,即比较S[(i+k)%L]和S[(j+k)%L]:- 如果相等,说明前
k个字符都一样,那就继续看下一个,k++。 - 如果
S[(i+k)%L] > S[(j+k)%L]:这意味着以i到i+k中任何一个位置 作为起点,都不可能是最小的(因为同样长度下,对应j那边的字符更小)。所以我们直接把i跳过这段区间,更新为i = i + k + 1。 - 如果
S[(i+k)%L] < S[(j+k)%L]:同理,以j开始的这段区间被淘汰,更新j = j + k + 1。
- 如果相等,说明前
- 如果某次跳跃后
i == j了,为了保证找的是两个不同的起点,让j++。 - 最后
k清零,继续新一轮比较。当i或j跑完一圈,或者k达到了 LLL,循环结束。最后min(i, j)就是我们要找的最小起点
翻译
现代数字计算机在概念上都是相似的,无论其尺寸大小。然而,根据成本和性能,它们可以被划分为几个类别:个人计算机(PC)或微型计算机,这是一种成本相对较低的机器,通常为台式机大小(尽管"笔记本电脑"小到可以装进公文包,而"掌上电脑"可以装进口袋);工作站,一种具有增强的图形和通信能力的微型计算机,使其特别适用于办公;小型计算机,通常对于个人使用来说过于昂贵,其性能适合企业、学校或实验室;以及大型计算机,这是一种大型且昂贵的机器,能够满足大型商业企业、政府部门、科研机构等的需求(其中最大和最快的被称为超级计算机)。
数字计算机并不是一台单一的机器:相反,它是一个由五个截然不同的部分组成的系统:(1)中央处理器(CPU);(2)输入设备;(3)内存存储设备;(4)输出设备;以及(5)被称为总线的通信网络,它将系统的所有部分连接起来,并将系统与外部世界相连。
relatively: adv. 相对地,比较地
