首页 > 编程语言 >算法学习笔记(15): Trie(字典树)

算法学习笔记(15): Trie(字典树)

时间:2023-02-08 12:22:05浏览次数:58  
标签:cnt kids 15 Trie int 字符串 节点 字典

Trie树

Trie(字典树)是一种用于实现字符串检索的多叉树。

Trie的每一个节点都可以通过 c 转移到下一层的一个节点。

我们可以看作可以通过某个字符转移到下一个字符串状态,直到转移到最终态为止。这是后话……

我们以插入了字符串 abaab 三个字符串的Trie树为例:

其实一看图就非常清晰了

在上图中,如果我们需要继续插入一个字符串 abc,那么就只需要新建一个节点即可

思路清晰,那么代码如何实现?

  • 首先是插入部分:
struct Node {
    int kids[65];
    int cnt;
} nodes[N];

#define kids(p, j) nodes[p].kids[j]
#define cnt(p) nodes[p].cnt

void insert(char * s, int len) {
    int p = 0;
    for (int i = 0; i < len; ++i) {
        int j = discrete(s[i]);
        if (!kids(p, j)) kids(p, j) = ++usage; // 新建节点
        p = kids(p, j);
    }
    ++cnt(p);
}

discrete指的是离散化,例如这里是将 a-z0-25 表示

最终的 cnt 表示有几个字符串在当前节点结束。

  • 然后是查询部分

我们还是利用类似的思路,一个一个向下走。

例如我们要查询字符串 aba,那么我们从根节点 0 开始,通过 a 走到 1 节点,通过 b 走到 4 节点,发现没有 a 的子节点,表明没有这个字串,结束寻找。

// 这里是查询这个字符串出现了多少次,为0就是没有出现
int count(char * s, int len) {
    int p = 0;
    for (int i = 0; i < len; ++i) {
        int j = discrete(s[i]);
        if (!kids(p, j)) return 0;
        p = kids(p, j);
    }
    return cnt(p);
}

其实主要操作就这两个,我们考虑一下空间和时间复杂度:

时间复杂度很明显是与字符串长度相关的,我们每处理一个字符走一个节点,也就是 \(O(L)\) 的复杂度,那么总的复杂度就是 \(O(NL)\)

至于空间复杂度,每处理一个字符串至多新建 \(L\) 个节点,那么就是 \(O(L)\) ,每一个节点的大小关乎字符串的字符集大小,所以我们认为是 \(O(C)\) 那么总共就是 \(O(NLC)\) ,但是,在实际中,远远达不到此复杂度(除非毒瘤出题人想卡你),例如最初的图,一共 4 个字符串,但是只有 5 个节点……

例题

【模板】字典树 - 洛谷

注意题意,以询问所给作为前缀,求有多少个字符串满足此前缀

那么我们需要魔改一下 insert 函数即可……将 ++cnt(p) 放入循环中即可

还请读者仔细思考

[USACO12DEC]First! G - 洛谷

这道题非常的神奇……考虑先建Trie树,如果某一个字符串的字典序比其他任何字符串都大,那么一定不存在为其前缀的字符串。

再考虑字典序,如果使 s 其字典序最大,那么每一个分叉点上,s[i] 比其他所有存在的分叉都要大。

如样例:omm, moo, mom

如果要使 omm 最大那么在第一层上满足 o > m,其他层上没有分叉。

如果要使 moo 最大,那么第一层上满足 m > o,第三层上满足 o > m,条件相悖,所以不可行。

其他同理。

那么我们如何判断条件相悖?可以借鉴 2-SAT 的思路,通过大于关系建图,如果存在环,那么不可行。

判环用拓扑,谁用Tarjan啊

最终,每一个串判断一遍即可。

[BJOI2016]IP地址 - 洛谷

这道题就是Trie的一种特殊用法。

有点类似线段树的区间标记。

我们考虑改变一个规则对其整个子树都有影响,那么我们考虑什么时候影响抵消?更深的点会阻挡了标记的下传。那么我们记录一下各个点的标记情况,通过类似线段树的方法下传标记即可。

正确性显然。

扩展

Trie树实际上是 AC自动机 和 回文自动机 等自动机的载体,需要经过一点点小变换。

在此不展开叙述,详见我的其他文章。

标签:cnt,kids,15,Trie,int,字符串,节点,字典
From: https://www.cnblogs.com/jeefy/p/17101290.html

相关文章

  • 【NOIP2015】【Vijos1979】信息传递(有向图最小环大小)
    problem给定一张n个点,n条边的有向图求图的最小环,输出大小solutionkosaraju暴力求出所有强连通分量,取最小值即可codes//kosaraju#include<iostream>#include<al......
  • 【NOIP2015】【Luogu2678】跳石头
    problemsolutioncodes//二分答案//QAQ注意:起点和终点也是有石头的w#include<iostream>#include<algorithm>#definemaxn100010usingnamespacestd;intll,n,......
  • 【NOIP2010】【Luogu1540】机器翻译
    problemsolutioncodes//STL大法好#include<iostream>#include<set>#include<queue>usingnamespacestd;queue<int>q;set<int>s;intmain(){intm,n,an......
  • 洛古P1518—两只塔姆沃斯牛
    题目描述两只牛逃跑到了森林里。FarmerJohn开始用他的专家技术追捕这两头牛。你的任务是模拟他们的行为(牛和John)。追击在10×1010\times1010×10的平面网格内进行。......
  • 【嵌入式】微芯旺KungFu32A156MQT使用PWM实现呼吸灯
    由于例程给我的IO口是G8,但是我的板子上没有暴露G8引脚,所以需要查看数据手册,重映射一个IO口作为CCP的PWM输出 例:可见PB10使用AF2重映射到CCP0的通道3上,所以使用PB10观......
  • CF1511G Chips on a Board
    CF1511GChipsonaBoard比较有启发性的一道题。询问是最简单的nim游戏,不难发现若一列上有两个棋子,那么这两个棋子对于答案是没有贡献的,因此可以令\(c_i\)表示第\(......
  • [Ynoi2015] 纵使日薄西山
    题传考虑一个\(a_i\)变为最大值的时候,由于它与两边的值相对大小不变,因此\(a_i\)造成的贡献必然是它自己,进一步地,题目操作可以简化为每次选择一个靠左地最大值,将其和左......
  • 代码随想录算法训练营Day07 | 454.四数相加II ,383. 赎金信 ,15. 三数之和 ,18. 四数
     454.四数相加II题目链接: 454.四数相加II-力扣(LeetCode)题目给你四个整数数组nums1、nums2、nums3和nums4,数组长度都是n,请你计算有多少个元组(i,j,k,l)......
  • 第15课、参数化-DDT xlrd
      二、基于上一节课的代码模块,加上ddt 有两个地方需要加修饰符1.测试类前面:@ddt.ddt2.测试用例前:@ddt.data-------有了ddt模块,就可以实现多组数据串行登录页面,......
  • OpenHarmony开发15 —— 消息队列
    OpenHarmony开发15——消息队列说点别的,这几天没更新真的是被这个消息队列折磨完了,谁知道鬼鸿蒙它不进行任何提示!为什么stackoverflow会不提示啊!!!太折磨了太折磨了......