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 真棒 字典树大概长这么个亚子 呕吼真棒 就是将读进去的字符串根据当前的字符是什么和所 ...
随机推荐
- (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server
用 JWT 机制实现验证的原理如下图: 认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...
- 数组-在Shell脚本中的基本使用介绍
Shell脚本在运维工作中是极其重要的,而数组在shell脚本里的运用无论是在循环或运算方面都是非常实用的一个环节.下面是对shell脚本中数组方面一些操作在此进行记录,希望能帮助到有兴趣的朋友~1. ...
- Linux内核及分析 第八周 进程的切换和系统的一般执行过程
学习笔记: 一.进程调度与进程调度的时机分析 1.不同类型的进程有不同需求的调度需求: 第一种分类: —I/O-bound:频繁的进行I/O,通常会花费很多时间等待I/O操作的完成 —CPU-boun ...
- WebAPI实例--第一个API
今天终于做了第一个任务,学习API之后的第一个实例.销售设置开发API. 第一.层次结构 1.API各层 项目结构主要有五层,分别为API.BizModel.Data.DBModel.Logic. 2 ...
- Voltage Keepsake CodeForces - 801C (思维+二分)
题目链接 这是一道很棒的二分题. 思路: 首先先思考什么情况下是可以无限的使用,即输出-1. 我们思考可知,如果每一秒内所有设备的用电量总和小于等于充电器每秒可以充的电,那么这一群设备就可以无限使用. ...
- opencv学习笔记(五)
线性滤波 方框滤波--boxblur函数 均值滤波(邻域平均滤波)--blur函数 高斯滤波--GaussianBlur函数 中值滤波--medianBlur函数 双边滤波--bilateralFil ...
- GSP事件探查器 无法进行跟踪的解决办法(场景之一)
使用GSP事件探查器 可以快速的定位问题, 跟踪效果很好 但是有时会出现 无法新建跟踪的问题. 这里有一个比较常见的解决办法 问题现象: 新建跟踪无效. 解决办法 其实就三步: 1. 打开 servi ...
- [转帖] iptables之四表五链
iptables之四表五链 http://www.cnblogs.com/clouders/p/6544584.html mark 学习一下 对防火墙一无所知.. iptables可谓是SA的看家本领 ...
- node.js依赖express解析post请求四种数据格式()
分别是这四种: www-form-urlencoded, form-data, application/json, text/xml www-form-urlencoded 这是http的post请求 ...
- mysql学习笔记二 —— 权限体系
要点: 1.MySQL的API2.MySQL的相关文件3.MySQL的权限体系 1.MySQL的API 应用程序接口 (application program interface) 1.1 命令行中的 ...