首页 > 其他分享 >洛谷每日一题(P1481 魔族密码)字典树解法

洛谷每日一题(P1481 魔族密码)字典树解法

时间:2024-09-29 15:52:33浏览次数:12  
标签:字符 P1481 洛谷 cur trienode 魔族 ump 节点 字典

原题目链接:

P1481 魔族密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

原题目截图:

思路分析:

这道题的话其实有很多种方法,可以用动态规划做,不过我一看到这道题,脑子里不禁蹦出一个数据结构:“字典树”!

字典树+深度优先搜索。

那么在这之前,我们先来了解一下什么是字典树吧!

什么是字典树:

字典树(Trie),也称为前缀树或Ternary Search Tree,是一种用于快速检索字符串数据集中的键的数据结构。它的优点在于可以快速地插入和查找字符串,并且可以有效地用于实现自动补全和拼写检查等功能。

字典树的特点:(有颜色的部分是重点)

  1. 共同前缀:字典树利用字符串的共同前缀来减少存储空间和提高查找效率。

  2. 节点:字典树由多个节点组成,每个节点表示一个字符。

  3. :节点之间的连接表示从父节点到子节点的字符。

  4. 根节点:字典树的根节点通常表示一个空字符串。

  5. 结束符:通常用一个标志(如isEndOfWord)来表示某个节点是一个单词的结束。

字典树的工作原理:

  • 插入:从根节点开始,对要插入的字符串的每个字符,检查是否存在相应的子节点。如果不存在,则创建一个新的节点。最后,标记该字符串的结束节点。

  • 查找:从根节点开始,按照要查找的字符串的字符顺序遍历节点。如果某个字符的子节点不存在,则表示该字符串不在字典树中。

  • 删除:从根节点开始,按照要删除的字符串的字符顺序遍历节点。对于每个节点,检查是否有其他子节点。如果没有,则可以删除该节点。

字典树的应用场景:

  1. 自动补全:在用户输入时,根据已输入的字符串提示可能的完整单词。

  2. 拼写检查:检查用户输入的单词是否拼写正确。

  3. IP 路由:在网络路由中,IP地址可以通过字典树快速匹配到相应的路由策略。

  4. 文本搜索:实现文本搜索算法,如字符串搜索和模式匹配。

怎么构建字典树:

我们知道了,字典树其实就是把字符串统计在一个树状结构里面,每个节点储存一个字符。

那么我们以题目所给的案例,来试着构建一颗字典树吧:

剩下的插入操作大同小异,我们这里直接看最终结果:

构建字典树的代码实现:

这道题的话,其实只涉及到插入操作。

我们先看看字典树应该有一个怎样的节点结构:

#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;

// 定义字典树的节点
struct trienode {
    char val;               // 节点代表的字符
    unordered_map<char, trienode*> ump;  // 存储子节点的哈希表,键是字符,值是指向该字符对应节点的指针
    bool iswordend;         // 标记该节点是否为一个单词的结束位置

    // 构造函数,初始化节点的值和结束标志
    trienode(char v) : val(v), iswordend(false) {}

    // 插入单词到字典树
    void insertword(string word) {
        trienode* cur = this;  // 从当前节点(根节点或父节点)开始
        for (char ch : word) {  // 遍历单词中的每个字符
            if (!cur->ump.count(ch)) {  // 如果当前节点没有该字符的孩子节点
                cur->ump[ch] = new trienode(ch);  // 创建一个新的节点并添加到哈希表中
            }
            cur = cur->ump[ch];  // 移动到下一个节点
        }
        cur->iswordend = true;  // 将单词的最后一个字符对应的节点标记为单词结束
    }
};

代码解释:
  • 结构体定义trienode是字典树的节点,包含一个字符val,一个哈希表ump用于存储子节点,以及一个布尔值iswordend用于标记单词的结束。
  • 构造函数:初始化节点的字符和结束标志。结束标志默认为false
  • insertword函数
    • 参数word是需要插入到字典树中的单词。
    • 过程
      • 使用一个指针cur从当前节点开始遍历。
      • 对于单词中的每个字符,检查当前节点是否有该字符的子节点。
      • 如果没有,创建一个新的节点并添加到哈希表中。
      • cur更新为该子节点,继续遍历下一个字符。
      • 遍历完成后,将单词的最后一个字符对应的节点标记为单词结束。

解决代码:

当你把字典树建好之后,你会发现这其实就是一道很简单的深度优先计数题了:

#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;

// 定义字典树的节点
struct trienode {
    char val;               // 节点代表的字符
    unordered_map<char, trienode*> ump;  // 存储子节点的哈希表,键是字符,值是指向该字符对应节点的指针
    bool iswordend;         // 标记该节点是否为一个单词的结束位置

    trienode(char v) : val(v), iswordend(false) {}  // 构造函数

    // 插入单词到字典树
    void insertword(string word) {
        trienode* cur = this;  // 从当前节点(根节点或父节点)开始
        for (char ch : word) {  // 遍历单词中的每个字符
            if (!cur->ump.count(ch)) {  // 如果当前节点没有该字符的孩子节点
                cur->ump[ch] = new trienode(ch);  // 创建一个新的节点并添加到哈希表中
            }
            cur = cur->ump[ch];  // 移动到下一个节点
        }
        cur->iswordend = true;  // 将单词的最后一个字符对应的节点标记为单词结束
    }
};

// 深度优先搜索(DFS)函数,用于找到最长的词链
void dfs(trienode* root, int &currentLength, int& maxn) {
    if (!root) return;  // 如果当前节点为空,则返回

    if (root->ump.empty()) {
        // 如果当前节点没有子节点,说明到达了叶子节点,即一条链路的终点
        maxn = max(maxn, currentLength);  // 更新最长链长度
        return;
    }

    for (auto& it : root->ump) {
        // 遍历当前节点的所有子节点
        if (it.second->iswordend) {
            // 如果子节点是单词的结尾,则增加当前链的长度
            currentLength++;
        }
        // 递归地对每个子节点进行深度优先搜索
        dfs(it.second, currentLength, maxn);
        if (it.second->iswordend) {
            // 如果子节点是单词的结尾,在回溯时减少当前链的长度
            currentLength--;
        }
    }
}

int main() {
    int n;
    cin >> n;
    trienode* root = new trienode('*'); // 头结点
    for (int i = 0; i < n; i++) {
        string word;
        cin >> word;
        root->insertword(word);
    }

    int maxn = 0;
    int c = 0;
    dfs(root, c, maxn); // 进行DFS搜索
    cout << maxn << endl; // 输出最长链长度

    // 释放内存
    // 注意:实际应用中需要递归释放字典树的所有节点内存
    return 0;
}

对于这里dfs的写法,开始我没有留意到符号&,在出错之后,我把这个小细节注意到了。

各位读者不妨比较一下上下两段代码中dfs函数的细微区别。

当然这里给出的两段代码都是正确的可运行代码。

#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;

// 定义字典树的节点
struct trienode {
    char val;
    unordered_map<char, trienode*> ump;
    bool iswordend;
   

    trienode(char v) : val(v), iswordend(false) {}

    // 插入
    void insertword(string word) {
        trienode* cur = this;
        for (char ch : word) {
            if (!cur->ump.count(ch)) {
                cur->ump[ch] = new trienode(ch);
            }
            cur = cur->ump[ch];
        }
        cur->iswordend = true;
    }
};

void dfs(trienode* root, int currentLength, int& maxn) {//currentLenth表示当前递归已有单词数
    if (!root) return;

    if (root->ump.empty()) {
        maxn = max(maxn, currentLength);//到叶子结点,即一条链路完
        return;
    }

    for (auto& it : root->ump) {
        if (it.second->iswordend) currentLength++;   
       // cout << "dfs(" << it.second->val << "," << currentLength << "," << maxn << ")" << endl;
            dfs(it.second, currentLength , maxn);
            
    }
}





int main() {
    int n;
    cin >> n;
    trienode* root = new trienode('*'); // 头结点
    for (int i = 0; i < n; i++) {
        string word;
        cin >> word;
        root->insertword(word);
    }

    int maxn = 0;
    int c = 0;
    dfs(root, c, maxn); // 进行DFS搜索
    cout << maxn << endl; // 输出最长链长度

   
  

    return 0;
}


今天的博客就写到这里了,很快就要国庆假期了,预祝诸君国庆快乐!

标签:字符,P1481,洛谷,cur,trienode,魔族,ump,节点,字典
From: https://blog.csdn.net/weixin_70448721/article/details/142620366

相关文章

  • 洛谷 P1672
    前缀和降低区间和查询问题的时间复杂度,分一维和二维一种数据预处理手段,一般配合其他算法查分、二分搜索二分:容斥原理。sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];差分前缀和相对的策略,可当做求和的逆运算a[l]++;a[r+1]--;洛谷P1672......
  • 洛谷P1827 [USACO3.4] 美国血统 题解
    上题目:题目描述农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而不是用图形的方法。你的任务是在被给予奶牛家谱的“树中序遍历”和......
  • 洛谷P1162 填涂颜色题解
    老规矩上题目:题目描述由数字 00 组成的方阵中,有一任意形状的由数字 11 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 22。例如:6×66×6 的方阵(n=6n=6),涂色前和涂色后的方阵如下:如果从某个 00 出发,只向上下左右 44 个方向移动且仅经过其他 00 的情况下,无法......
  • 【洛谷】P4819 [中山市选] 杀人游戏 的题解
    【洛谷】P4819[中山市选]杀人游戏的题解题目传送门题解Tarjan我可爱的Tarjan嘻嘻qaq枚举每一个点,然后枚举每条出边,如果相连的两个点在同一个强连通分量中,或者xx......
  • 【洛谷】AT_abc178_d [ABC178D] Redistribution 的题解
    【洛谷】AT_abc178_d[ABC178D]Redistribution的题解洛谷传送门AT传送门题解一个水水的动态规划,阿巴巴巴。题目大概是这样:给定一个正整数SSS,问有多少个数满足以......
  • 【洛谷】P1168 中位数 的题解
    【洛谷】P1168中位数的题解题目传送门题解不懂就问,这是什么标签啊,《线段树》《二分》《堆》《树状数组》qaq谔谔,教练讲的这是一题线段树,看半天看不出来,也不会做,只好去看题解。其他的题解讲什么主席树,平衡树,分块,结果看完第一篇人都傻了。什么东西嘛qaq(恼vector直接一......
  • 洛谷题单指南-分治与倍增-P4155 [SCOI2015] 国旗计划
    原题链接:https://www.luogu.com.cn/problem/P4155题意解读:在m个点的环上,有n个区间,且各个区间之间没有包含关系,计算从每个区间开始,最少要多少个区间能覆盖环上所有m个点。解题思路:本质上是一个区间覆盖问题!1、破环成链由于题目中是一个环,对于环的问题,在区间DP中介绍过,经典处理......
  • 洛谷每日一题(P1540 [NOIP2010 提高组] 机器翻译)
    原题目链接:P1540[NOIP2010提高组]机器翻译-洛谷|计算机科学教育新生态(luogu.com.cn)原题目截图:思路分析:读懂题意,直接模拟过程即可。这是一道很简单的题目。思路过程很类似模拟页面置换算法中的先进先出(FIFO)策略。因此我们很容易想到,要用队列来实现。下面是......
  • 洛谷 P2241 统计方形(数据加强版)
    统计方形(数据加强版)题目背景1997年普及组第一题题目描述有一个n×mn\timesmn×m方格的棋盘,求其方格包......
  • 洛谷题单指南-分治与倍增-P3517 [POI2011] WYK-Plot
    原题链接:https://www.luogu.com.cn/problem/P3517题意解读:有n个连续的点p1,p2,...,pn,将这n个点分成不超过m堆,每堆点连续,每一堆都缩成一个点qi,要使得原来的点p1~ps距离qi的最大值最小(最相似),求这个相似度,并计算一共分成几堆,以及每堆缩到的点qi的坐标。解题思路:要使得若干点缩到一......