TRIE 字典树 前缀紧急集合!
TRIE:
在计算机科学中,Trie,又称前缀树或字典树,是一种有序树状的数据结构,用于保存关联数组,其中的键通常是字符串。——百度百科
自我理解:
trie树,是一种处理字符串前缀的数据结构,通常会有N*Len个节点,每个节点又引申出|S|个子节点指针,相当于一个很多叉的树,(甚至往往每个点叉的个数比高度还多)我们可以O(n)把待处理的字符串“挂到”trie上,最后统一查询,或者边挂边查。
可以发现,每个节点到根节点的路径就是一个前缀。
为什么要用字典树?
我们处理前缀问题的时候,往往需要求前缀的最值问题,公共前缀等等。朴素的做法都是要一个一个枚举,而我们把这些字符串集中到一个树上,通过公共前缀共用节点的特点,可以巧妙地不经过一一比较,就可以判断。
例如:
1.所有字符串LCP问题,朴素做法要处理hash,再在每个字符串上二分。logL* N,并且不能保证完全的正确性。毕竟有误差可能性。
通过trie树,相同的前缀已经被我们集中到了一起,我们只需要从根节点开始,一直找t[t[u].son].v==n的son节点,直到找不到为止,避免了对每个字符串进行操作的O(n)。
虽然预处理复杂度NlogL,但是查找的复杂度只有|S|*L,很少了。对于后续处理来说,预处理复杂度算不了什么。
2.两两字符串LCP问题:见例题:JZOJ 3126【GDKOI2013选拔】大LCP
但是,缺点很明显,trie的空间要更大,N太长就不行了。
需要的东西:
1.struct:son[28](如果是不分大小写的字典树),vis(该节点被访问过几次,或者:已经有多少个串拥有该节点代表的前缀,用于求LCP),num(编号为num的串结尾在这里,用于dfs确定字符串的字典序)2.insert:(洛谷2412)
void insert(char b[],int id)
{
int len=strlen(b);
int u=;
for(int i=;i<=len-;i++){
if(!t[u].son[tol(b[i])]) {
t[u].son[tol(b[i])]=++tot;
u=tot;
}
else u=t[u].son[tol(b[i])];
}
t[u].num=id;
}
id:字符串编号,注意每次从根节点0开始插入。第一个有实际意义的点必须从1开始,根什么都不代表,只有指针。但是有编号0。
3.dfs
void dfs(int x){
if(t[x].num) ran[t[x].num]=++cnt;
for(int i=;i<=;i++){
if(t[x].son[i]) dfs(t[x].son[i]);
}
}
确定所有插到trie上的字符串的字典序。
4.查找字符串,就直接找。
如果这个节点没有son[x[i]]这个出边,则返回没有;否则继续找,直到x[]找到底,判断这个点num是否为0,0返回没有,非0返回有。
5.查询任意两个字符串的LCP:两字符串对应的末尾节点,求LCA的深度,就是LCP
应用例题:(也有不是处理字符串的)
T1:JZOJ 3126【GDKOI2013选拔】大LCP
Description
LCP就是传说中的最长公共前缀,至于为什么要加上一个大字,那是因为…你会知道的。
首先,求LCP就要有字符串。既然那么需要它们,那就给出n个字符串好了。
于是你需要回答询问大LCP,询问给出一个k,你需要求出前k个字符串中两两的LCP最大值是多少,这就是传说中的大LCP。
Input
第一行一个整数N,Q,分别表示字符串个数和询问次数。
接下来N行,每行一个字符串。
再Q行,每行一个正整数k。
对于100%的数据,字符串总长度不超过10^6,1<=N,Q<=10^5.
分析:
这个题可以显著地体现trie求LCP的霸气所在。狂虐hash。
我们要是先都插入trie再处理询问,我怎么知道哪些是前k个产生的贡献?
询问是不强制在线的,所以把k从小到大排个序。按顺序插入,到了一个询问就输出正在更新的mx即可。
每次插入,直到到了一个要建新节点之前的所有经过的点,就是这个字符串与之前所有插入过的字符串的LCP长度。
例如叫做i字符串,都相当于是一个与1~i-1字符串进行LCP,直接O(1)带走啊。
相比较于hash,就可怜多了,必须n^2枚举字符串对,再二分LCP,n^2logL,哭死。
trie直接O(n),边插边查,复杂度大大下降。
前缀集合,trie确实优秀。
T2:poj2001
给定若干字符串,对于每个字符串求出一个最短前缀,使得这个前缀不是任何其他字符串的前缀。
分析:直接都挂上去,记录vis标记(见开头)。每个字符串按图索骥,直到某个点vis为1
T3:bzoj2251
给定一个长度为N的01串,要求按照字典序输出所有出现次数大于一次的子串的出现次数。
N<=3000。
分析:这个题有点想法。
我们知道,所有前缀的所有后缀就是所有子串,但是我们不能挂前缀啊,后缀怎么处理??
trie是处理前缀的。
不过还有一句话:所有后缀的所有前缀就是所有子串!!所以我们挂所有后缀。
节点标记vis,最后dfs按先0后1的顺序找到所有vis大于1的就行了。
为什么呢?因为vis=1,说明挂上的字符串中有一个从该点到根的前缀,而vis>1就说明有多个。
而我们挂上去的是后缀,有一个前缀,就有一个子串,有多个相同的前缀,就有多个相同的子串。等价转化。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
const int N=+;
struct trie{
int son[];
int v;
}t[N*N/];
int tot;
int n,len;
char a[N];
char b[N];
void add(char x[],int l){
//cout<<x<<endl;
int u=;
for(int i=;i<l;i++){
int b=x[i]-'';
if(t[u].son[b]){
u=t[u].son[b];
}
else{
t[u].son[b]=++tot;u=tot;
}
t[u].v++;
}
}
void dfs(int x){
if(t[x].v>) printf("%d\n",t[x].v);
if(t[x].son[]) dfs(t[x].son[]);
if(t[x].son[]) dfs(t[x].son[]);
}
int main()
{
scanf("%d",&n);
scanf("%s",a+);
for(int i=n;i>=;i--)
{
for(int j=i;j<=n;j++){
b[j-i]=a[j];
}
add(b,strlen(b));
//if(i!=1) memset(b,0,sizeof b);
}
dfs();
return ;
}
bzoj2251
T4:洛谷P4551
题目描述
给定一棵 n 个点的带权树,结点下标从 1 开始到 N 。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
数据范围
1≤n≤100000; 0<u,v≤n; 0≤w<2^31
分析:
这个题就比较考验洞察能力了。
因为异或运算满足交换律,结合律。所以我们可以求出每个点到根节点路径上的异或和dis[i]。
这样,我们要把所有的dis(二进制位)从高位到低位,挂到trie上。
对于节点i,先插入,再查询,将dis[i]高位补0对齐31位,从trie上往下找,每次先找有没有相反的,dis[i]这一位是0,找有没有1,反之找0
如果没有相反的,只能进入相同的了,然后指针后移,继续进行这个操作。
因为是从高位开始匹配,所以肯定尝试找在高位能异或出来1的可能性。
仍然利用前缀集合起来的性质,避免了枚举点对,O(n)扫一遍就好了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ui;
const int N=+;
const int M=;
int ch[*N][];
int tot=;
ui dis[N];
struct node{
int nxt,to;
ui val;
}bian[*N];
int hd[N];
int cnt;
int n;
ui ans,sum;
void add(int x,int y,ui z)
{
bian[++cnt].nxt=hd[x];
bian[cnt].to=y;
bian[cnt].val=z;
hd[x]=cnt;
}
void dfs(int x,ui dist,int fa)
{
dis[x]=dist;
for(int i=hd[x];i;i=bian[i].nxt)
{
int y=bian[i].to;
if(y==fa) continue;
if(x!=) dfs(y,dist^bian[i].val,x);
else dfs(y,bian[i].val,x);
}
}
ui work(ui x)
{
ui st=;
ui sum=;
int now=;
while(st)
{
int kk=((unsigned int)x&((unsigned int)<<st-))>>(st-);
if(ch[now][!kk]) {
now=ch[now][!kk],sum=sum+((unsigned int)<<st-);
}
else {
now=ch[now][kk];
}
st--;
}
return sum;
}
void puts(ui x)
{
ui st=;
int now=;
while(st)
{
int kk=((unsigned int)x&((unsigned int)<<st-))>>(st-);
if(ch[now][kk]) now=ch[now][kk];
else ch[now][kk]=++tot,now=tot;
st--;
}
}
signed main()
{
scanf("%lld",&n);
int x,y,z;
for(int i=;i<=n-;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs(,,-);
for(int i=;i<=n;i++)
ans=max(ans,dis[i]);//warning!!
puts(dis[]);
for(int i=;i<=n;i++)
{
ans=max(ans,work(dis[i]));
puts(dis[i]);
}
printf("%lld",ans);
return ;
}
洛谷4551
TRIE 字典树 前缀紧急集合!的更多相关文章
- 算法导论:Trie字典树
1. 概述 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树. Trie一词来自retrieve,发音为/tr ...
- 9-11-Trie树/字典树/前缀树-查找-第9章-《数据结构》课本源码-严蔚敏吴伟民版
课本源码部分 第9章 查找 - Trie树/字典树/前缀树(键树) ——<数据结构>-严蔚敏.吴伟民版 源码使用说明 链接☛☛☛ <数据结构-C语言版>(严蔚 ...
- 数据结构 -- Trie字典树
简介 字典树:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种. 优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高. 性质: 1. 根节 ...
- 萌新笔记——C++里创建 Trie字典树(中文词典)(一)(插入、遍历)
萌新做词典第一篇,做得不好,还请指正,谢谢大佬! 写了一个词典,用到了Trie字典树. 写这个词典的目的,一个是为了压缩一些数据,另一个是为了尝试搜索提示,就像在谷歌搜索的时候,打出某个关键字,会提示 ...
- Trie字典树 动态内存
Trie字典树 #include "stdio.h" #include "iostream" #include "malloc.h" #in ...
- 标准Trie字典树学习二:Java实现方式之一
特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! 系列文章: 1. 标准Trie字典树学习一:原理解析 2.标准T ...
- 817E. Choosing The Commander trie字典树
LINK 题意:现有3种操作 加入一个值,删除一个值,询问pi^x<k的个数 思路:很像以前lightoj上写过的01异或的字典树,用字典树维护数求异或值即可 /** @Date : 2017- ...
- C++里创建 Trie字典树(中文词典)(一)(插入、遍历)
萌新做词典第一篇,做得不好,还请指正,谢谢大佬! 写了一个词典,用到了Trie字典树. 写这个词典的目的,一个是为了压缩一些数据,另一个是为了尝试搜索提示,就像在谷歌搜索的时候,打出某个关键字,会提示 ...
- 踹树(Trie 字典树)
Trie 字典树 ~~ 比 KMP 简单多了,无脑子选手学不会KMP,不会结论题~~ 自己懒得造图了OI WIKI 真棒 字典树大概长这么个亚子 呕吼真棒 就是将读进去的字符串根据当前的字符是什么和所 ...
随机推荐
- Promise 原理
异步:可同时好几件事,互不影响: 同步:按循序一件一件.... 异步好多缺点:.... promise就是解决异步计算的这些缺点的,主要用于: 1.异步计算: 2.可以将异步操作队列化 按期望的顺序 ...
- 个人博客作业_week3
一. 评测 1.对方背景 这个好像大家都不一样,他要考四级啊,考六级啊,出国啊,或者平时写代码看不懂错误信息(呵呵)(还有可能是为了完成某次作业而用的....), 等等,所以是会用的.一般的问题都能解 ...
- github的使用心得
我的github地址:https://github.com/gaino1/test GitHub 是一个用于使用Git版本控制系统的项目的基于互联网的存取服务. GitHub可以托管各种git库,并提 ...
- 过滤器Filter的使用(以登录为例子)
使用过滤器步骤: (1)在web.xml文件中添加过滤器(以下例子是过滤多个请求) <!-- 用户登录过滤 --> <filter> <filter-name>lo ...
- ios开发之--CAKeyframeAnimation的详细用法
简单的创建一个带路径的动画效果,比较粗糙,不过事先原理都是一样的, 代码如下: 1,创建动画所需的view -(void)creatView { moveView = [UIView new]; mo ...
- jQuery(四)
get():把jQuery转化成原生js <script> $(function(){ //alert($('#div1').get(0).innerHTML); //jQuery里面也有 ...
- PAT 1036 跟奥巴马一起编程
https://pintia.cn/problem-sets/994805260223102976/problems/994805285812551680 美国总统奥巴马不仅呼吁所有人都学习编程,甚至 ...
- Debian中APT的前世今生
https://baike.baidu.com/item/apt-get/2360755 https://www.debian.org/doc/manuals/debian-handbook/sect ...
- Win2012r2 以及win2016 安装.NET3.5
自从微软的内核 6.2以上的版本之后 win2012 win2016 已经自带了 .net4.0的版本 但是很多应用还需要.net 3.5的版本,虽然微软的安装盘里面有 .net 3.5的安装文件,但 ...
- php的四种基本算法
/* 冒泡算法:结果从小到大,规则类似波浪推动的沙滩,先初始阈值为 0,初始第一次波浪之后,如果发现有左值比右边的大,就改变阈值并且完成波浪推动,重新初始化阈值为0,如此往复,直到没有阈值改变的情况出 ...