洛谷 B3927 [GESP202312 四级] 小杨的字典

新的一周......(laugh)

恩师:hnjzsyjyj

大家好!今天我们要一起解决洛谷上的一道题目 ——B3927 [GESP202312 四级] 小杨的字典。这道题看起来有点绕,但只要我们一步步拆解代码,就能轻松搞懂它的逻辑。接下来,我会用最直白的话,把这道题的解法和代码细节讲清楚,内容完全可以做成 PPT,方便大家理解和复习。

一、题目到底要我们做什么?

在开始看代码之前,我们得先明白题目要解决的问题。简单来说,这道题是关于一个 “字典替换” 的任务,具体要求如下:

  1. 首先会输入一个整数n,表示有n组 “单词映射”(就像字典里的 “钥匙” 和 “对应值”)。
  2. 然后输入n行,每行是两个字符串ab,表示 “用b替换a”(比如输入apple 苹果,就意味着后面遇到apple这个单词时,要换成 “苹果”)。
  3. 最后输入一个字符串s,我们需要处理这个字符串:
    • 把字符串里连续的小写字母(a-z)看作一个 “单词”。
    • 每个单词如果在前面的映射里存在,就替换成对应的b;如果不存在,就替换成 “UNK”。
    • 字符串里的非小写字母(比如数字、大写字母、符号等)不看作单词,直接保留原样。

举个例子帮助理解:

  • 假设输入的映射是:apple 苹果banana 香蕉
  • 输入的字符串s是:I like apple and banana!
  • 处理后应该输出:I like 苹果 and 香蕉!

再复杂一点的例子:

  • 映射:cat 猫
  • 输入s是:cat123Cat
  • 这里 “cat” 是小写字母组成的单词,替换成 “猫”;“123” 是非小写字母,直接保留;“Cat” 里的 “C” 是大写,不算小写单词,所以整个 “Cat” 不看作单词,直接保留。最终输出:猫123Cat

清楚了题目要求,我们再来看看代码是怎么实现这个过程的。

二、完整代码展示

先把要讲解的代码完整展示出来,大家可以先有个整体印象:

cpp

运行

#include<bits/stdc++.h>
using namespace std;
map<string,string> m;
string a,b,s,t,ans;
int n;
int main() {
    cin>>n;
    while(n--){
        cin>>a>>b;
        m[a]=b;
    }
    cin>>s;
    for(int i=0;i<s.size();i++){
        if(s[i]>='a'&&s[i]<='z')t+=s[i];
        else{
            if(t!=""){
                if(m.count(t))ans+=m[t];
                else ans+="UNK";
                t="";
            }
            ans+=s[i];
        }
    }
    if(t!=""){
        if(m.count(t))ans+=m[t];
        else ans+="UNK";
        t="";
    }
    cout<<ans;
    return 0;
}

这段代码不长,但每一行都有它的作用。接下来,我们就像拆积木一样,把代码一点点拆开来看。

三、代码拆解第一步:头文件和命名空间

代码第一行:#include<bits/stdc++.h>
这行代码我们可以理解为 “打包导入所有常用工具”。在 C++ 里,要使用输入输出、字符串、map 等功能,需要导入对应的 “头文件”(比如输入输出要用#include<iostream>,字符串要用#include<string>)。而bits/stdc++.h是一个 “万能头文件”,它包含了几乎所有常用的头文件,写题时用它可以省去一个个导入的麻烦,非常方便。

第二行:using namespace std;
这行代码的作用是 “简化代码书写”。C++ 里很多常用的东西(比如cincoutstringmap)都放在std这个 “命名空间” 里。如果没有这行,我们用cout就得写成std::cout,很啰嗦。加上这行后,就可以直接写cout了。

四、代码拆解第二步:变量定义

接下来是变量定义部分,一共定义了 5 个变量,我们一个个说:

  1. map<string,string> m;
    map是 C++ 里的一种 “键值对” 容器,就像一个 “字典”—— 你可以通过 “键”(key)快速找到对应的 “值”(value)。这里map<string,string>表示键和值都是字符串类型。比如我们可以存m["apple"] = "苹果",之后只要查m["apple"]就能得到 “苹果”。这个m就是用来存储题目中的n组映射关系的。

  2. string a,b,s,t,ans;
    这是 5 个字符串变量,作用分别是:

    • ab:临时存储输入的每一组映射(a是要被替换的单词,b是替换后的内容)。
    • s:存储最后输入的、需要处理的原始字符串。
    • t:临时存储正在拼接的 “小写字母单词”(比如遍历s时,遇到连续的小写字母就一个个加到t里)。
    • ans:存储处理后的结果字符串(最终要输出的就是它)。
  3. int n;
    整数变量,用来存储映射的组数(也就是题目中的n)。

五、代码拆解第三步:主函数(main)整体流程

main函数是程序的入口,所有代码从这里开始执行。我们先看整体流程,再细化每一步:

  1. 读取映射的组数n
  2. 循环n次,每次读取一组ab,存入map m中。
  3. 读取需要处理的字符串s
  4. 遍历s中的每个字符,处理成结果ans
  5. 输出ans

接下来,我们一步步看这些过程是怎么实现的。

六、代码拆解第四步:读取映射关系

代码:

cpp

运行

cin>>n;
while(n--){
    cin>>a>>b;
    m[a]=b;
}

这部分的作用是 “把所有映射关系存到 map 里”。

  • 首先cin>>n:从键盘输入n(比如输入 3,就表示有 3 组映射)。
  • 然后while(n--):这是一个循环,会执行n次(比如n初始是 3,第一次循环后n变成 2,直到n变成 0 时循环结束)。
  • 循环里的cin>>a>>b:每次输入两个字符串ab(比如第一次输入apple 苹果,那么a就是 "apple",b就是 "苹果")。
  • m[a] = b:把ab的对应关系存到map m里。这时候m就像一个字典,查a就能得到b

举个例子:如果输入:

plaintext

2
cat 猫
dog 狗

那么循环执行 2 次:

  • 第一次:a="cat"b="猫",执行m["cat"] = "猫"m现在有{"cat": "猫"}
  • 第二次:a="dog"b="狗",执行m["dog"] = "狗"m现在有{"cat": "猫", "dog": "狗"}

这样,后续需要替换时,直接查m就可以了。

七、代码拆解第五步:读取待处理字符串

代码:cin>>s;
这行很简单,就是把我们要处理的字符串输入到s里。比如输入I have a cat!,那么s就存储了这个字符串。

这里有个小细节:cin>>s默认会忽略空格,直到遇到空格或换行就停止读取。但在这道题里,题目中的输入字符串s可能包含空格吗?根据题目描述,s是一个 “字符串”,可能包含各种字符,包括空格。不过在实际测试中,cin>>s可能无法读取带空格的字符串,这时候可能需要用getline(cin, s)。但原代码用了cin>>s,可能题目中的测试数据里s没有空格,或者我们可以理解为代码简化了输入方式,核心逻辑不变。

八、代码拆解第六步:核心逻辑 —— 处理字符串s

这部分是整个代码最关键的地方,我们仔细看:

cpp

运行

for(int i=0;i<s.size();i++){
    if(s[i]>='a'&&s[i]<='z')t+=s[i];
    else{
        if(t!=""){
            if(m.count(t))ans+=m[t];
            else ans+="UNK";
            t="";
        }
        ans+=s[i];
    }
}

这段代码的作用是 “遍历s的每个字符,把小写字母组成单词,替换后加到结果里,非小写字母直接加到结果里”。

我们先解释循环的整体逻辑:for(int i=0;i<s.size();i++)表示从第 0 个字符开始,逐个遍历s中的所有字符(s.size()s的长度,比如s是 "abc",长度是 3,就遍历 i=0、1、2)。

接下来分两种情况处理每个字符s[i]

情况 1:当前字符是小写字母(s[i] >= 'a' && s[i] <= 'z'

代码:if(s[i]>='a'&&s[i]<='z')t+=s[i];
如果当前字符是小写字母(比如'a''z'中的一个),就把它加到临时字符串t里。t的作用是 “拼接当前正在形成的单词”。

举个例子:假设s是 "cat123",遍历过程中:

  • i=0 时,s[0]是 'c'(小写),t变成 "c"。
  • i=1 时,s[1]是 'a'(小写),t变成 "ca"。
  • i=2 时,s[2]是 't'(小写),t变成 "cat"。

情况 2:当前字符不是小写字母(比如数字、大写字母、符号、空格等)

代码:

cpp

运行

else{
    if(t!=""){
        if(m.count(t))ans+=m[t];
        else ans+="UNK";
        t="";
    }
    ans+=s[i];
}

这种情况下,说明 “当前的单词已经结束了”(因为遇到了非小写字母),需要处理之前存在t里的单词,然后把这个非小写字母直接加到结果里。

我们分步骤解释:

  1. if(t != ""):先判断t是不是空的。如果t不是空,说明里面存了一个完整的单词(比如前面例子中的 "cat"),需要处理。

    • m.count(t):判断这个单词t是否在map m中存在(也就是有没有对应的映射)。
      • 如果存在(m.count(t)为真),就把m[t](对应的替换值)加到ans里。
      • 如果不存在,就把 "UNK" 加到ans里。
    • t = "":处理完后,把t清空,准备接收下一个单词。
  2. ans += s[i]:把当前这个非小写字母直接加到结果ans里(因为它不需要替换)。

继续用前面的例子s = "cat123"

  • i=3 时,s[3]是 '1'(非小写字母),进入 else:
    • t是 "cat"(非空),检查m中是否有 "cat"。假设m里有"cat": "猫",所以ans加上 "猫"。
    • t清空为 ""。
    • 然后ans加上 '1',此时ans是 "猫 1"。
  • i=4 时,s[4]是 '2'(非小写字母),进入 else:
    • t是空的(因为刚清空),所以不处理t
    • ans加上 '2',ans变成 "猫 12"。
  • i=5 时,s[5]是 '3'(非小写字母),进入 else:
    • t是空的,不处理。
    • ans加上 '3',ans变成 "猫 123"。

九、代码拆解第七步:处理字符串末尾的单词

代码:

cpp

运行

if(t!=""){
    if(m.count(t))ans+=m[t];
    else ans+="UNK";
    t="";
}

这部分是为了处理一种特殊情况:如果字符串s的最后几个字符是小写字母(形成一个单词),那么在前面的循环中,这个单词会存在t里,但循环结束后并没有处理它(因为循环只在遇到非小写字母时才处理t)。

比如s是 "dogcat"(全是小写字母),循环遍历完所有字符后,t里会是 "dogcat",但循环里没有处理它,所以需要在循环结束后单独处理。

处理逻辑和前面一样:如果t非空,就检查是否在m中,替换后加到ans里,再清空t

举个例子:s = "dogcat"m中有"dog": "狗""cat": "猫"(注意这里s是 "dogcat",是连续的小写字母,会被当作一个单词):

  • 循环遍历所有字符后,t是 "dogcat"(因为没有遇到非小写字母,所以循环里没处理)。
  • 执行这段代码:t非空,检查m中是否有 "dogcat"。假设没有,所以ans加上 "UNK",最终ans是 "UNK"。

再比如s = "apple"m中有"apple": "苹果"

  • 循环结束后,t是 "apple",执行这段代码,ans加上 "苹果",最终输出 "苹果"。

十、代码拆解第八步:输出结果

代码:cout<<ans;
这行代码把处理好的结果字符串ans输出到屏幕上,就是我们要的答案。

最后return 0;表示程序正常结束。

十一、完整示例演示

为了让大家更清楚,我们用一个完整的例子从头到尾走一遍代码的执行过程。

示例输入:

plaintext

3
hello 你好
world 世界
test 测试
Hello, world! Let's test.

代码执行步骤:

  1. 读取n=3
  2. 循环 3 次读取映射:
    • 第一次:a="hello"b="你好" → m["hello"] = "你好"
    • 第二次:a="world"b="世界" → m["world"] = "世界"
    • 第三次:a="test"b="测试" → m["test"] = "测试"
  3. 读取s = "Hello, world! Let's test."(注意这里的大小写和符号)。
  4. 遍历s中的每个字符(为了方便,我们标上索引和字符):

索引 i 字符 s [i] 类型 处理过程 t 的值 ans 的值
0 'H' 非小写字母 t 是空,直接加 'H' 到 ans "" "H"
1 'e' 小写字母 加到 t "e" "H"
2 'l' 小写字母 加到 t "el" "H"
3 'l' 小写字母 加到 t "ell" "H"
4 'o' 小写字母 加到 t "ello" "H"
5 ',' 非小写字母 t 是 "ello"(非空),查 m:没有 "ello" → 加 "UNK" 到 ans;t 清空;加 ',' 到 ans "" "HUNK,"
6 ' ' 非小写字母 t 是空,直接加 ' ' 到 ans "" "HUNK, "
7 'w' 小写字母 加到 t "w" "HUNK, "
8 'o' 小写字母 加到 t "wo" "HUNK, "
9 'r' 小写字母 加到 t "wor" "HUNK, "
10 'l' 小写字母 加到 t "worl" "HUNK, "
11 'd' 小写字母 加到 t "world" "HUNK, "
12 '!' 非小写字母 t 是 "world"(非空),查 m:有→加 "世界" 到 ans;t 清空;加 '!' 到 ans "" "HUNK, 世界!"
13 ' ' 非小写字母 t 是空,直接加 ' ' 到 ans "" "HUNK, 世界!"
14 'L' 非小写字母 t 是空,直接加 'L' 到 ans "" "HUNK, 世界!L"
15 'e' 小写字母 加到 t "e" "HUNK, 世界!L"
16 't' 小写字母 加到 t "et" "HUNK, 世界!L"
17 ''' 非小写字母 t 是 "et"(非空),查 m:没有→加 "UNK" 到 ans;t 清空;加 ''' 到 ans "" "HUNK, 世界!LUNK'"
18 's' 小写字母 加到 t "s" "HUNK, 世界!LUNK'"
19 ' ' 非小写字母 t 是 "s"(非空),查 m:没有→加 "UNK" 到 ans;t 清空;加 ' ' 到 ans "" "HUNK, 世界!LUNK'UNK"
20 't' 小写字母 加到 t "t" "HUNK, 世界!LUNK'UNK"
21 'e' 小写字母 加到 t "te" "HUNK, 世界!LUNK'UNK"
22 's' 小写字母 加到 t "tes" "HUNK, 世界!LUNK'UNK"
23 't' 小写字母 加到 t "test" "HUNK, 世界!LUNK'UNK"
24 '.' 非小写字母 t 是 "test"(非空),查 m:有→加 "测试" 到 ans;t 清空;加 '.' 到 ans "" "HUNK, 世界!LUNK'UNK 测试."

  1. 循环结束后,检查t是否为空(此时t是空的,因为最后一个字符是 '.',已经处理过)。
  2. 输出ansHUNK, 世界! LUNK'UNK 测试.

十二、代码为什么能解决问题?

回顾整个代码,它能正确解决问题的关键在于:

  1. map存储映射关系,实现了快速查找(判断一个单词是否存在映射,只需要m.count(t),效率很高)。
  2. t临时存储连续的小写字母,准确区分 “单词” 和 “非单词”。
  3. 遍历字符串时,遇到非小写字母就处理当前的t(替换或 UNK),保证每个单词都被正确处理。
  4. 循环结束后单独处理t,解决了 “字符串末尾是单词” 的特殊情况。

这些逻辑覆盖了题目的所有要求:区分单词和非单词、替换存在的单词、未知单词用 UNK 代替、保留非单词字符。

十三、可能遇到的问题和注意事项

在理解或使用这段代码时,有几个细节需要注意:

  1. 单词的定义:题目中 “单词” 仅指连续的小写字母(a-z),大写字母、数字、符号、空格等都不属于单词。比如 "Apple" 中的 "A" 是大写,所以整个 "Apple" 不会被当作单词处理,会直接保留。

  2. 空字符串t的处理:代码中多次判断t != "",这是为了避免处理空字符串(比如连续遇到多个非小写字母时,t是空的,不需要替换)。

  3. 输入带空格的字符串:原代码用cin>>s读取字符串,这会导致无法读取带空格的s(因为cin>>遇到空格会停止)。如果题目中的s包含空格,应该用getline(cin, s),但需要注意在cin>>n之后可能有换行符残留,需要用cin.ignore()清除。修改后的代码可以是:

    cpp

    运行

    cin>>n;
    cin.ignore(); // 清除换行符
    while(n--){
        cin>>a>>b;
        m[a]=b;
    }
    getline(cin, s); // 读取带空格的字符串
    
  4. 映射中的单词是否区分大小写:题目中输入的ab是字符串,代码中map是区分大小写的。比如如果输入Apple 苹果,那么只有遇到 "Apple"(大写 A)才会替换,而 "apple"(小写 a)不会被替换。但根据题目描述,a应该是小写字母组成的(因为要替换的是小写单词),所以输入的a通常都是小写。

十四、总结

到这里,我们已经把洛谷 B3927 [GESP202312 四级] 小杨的字典这道题的代码完全拆解完了。这段代码虽然简短,但逻辑非常清晰:

  1. map存储映射关系,方便快速查找。
  2. 遍历待处理字符串,用t拼接小写字母组成单词。
  3. 遇到非小写字母时,处理当前单词(替换或 UNK),并保留非小写字母。
  4. 最后处理可能留在t里的末尾单词。

通过这个过程,我们能准确完成题目要求的替换任务。理解这道题不仅能帮助我们应对 GESP 四级考试,还能掌握map的使用、字符串遍历、条件判断等常用编程技巧。

希望这篇讲解能让大家彻底搞懂这道题和对应的代码,如果有任何疑问,欢迎一起讨论!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐