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=0;
    for(int i=0;i<=len-1;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=1;i<=26;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=3000+5;
struct trie{
    int son[2];
    int v;
}t[N*N/2];
int tot;
int n,len;
char a[N];
char b[N];
void add(char x[],int l){
    //cout<<x<<endl;
    int u=0;
    for(int i=0;i<l;i++){
        int b=x[i]-'0';
        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>1) printf("%d\n",t[x].v);
    if(t[x].son[0]) dfs(t[x].son[0]);
    if(t[x].son[1]) dfs(t[x].son[1]);
}
int main()
{
    scanf("%d",&n);
    scanf("%s",a+1);
    for(int i=n;i>=1;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(0);
    return 0;
}
bzoj2251

 

 

T4:洛谷P4551

题目描述

给定一棵 n 个点的带权树,结点下标从 1 开始到 N 。寻找树中找两个结点,求最长的异或路径。

异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

数据范围

1n100000; 0<u,vn; 0w<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=100000+10;
const int M=33;
int ch[33*N][2];
int tot=1;
ui dis[N];
struct node{
    int nxt,to;
    ui val;
}bian[4*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!=1) dfs(y,dist^bian[i].val,x);
        else dfs(y,bian[i].val,x);
    }
}
ui work(ui x)
{
    ui st=31;
    ui sum=0;
    int now=1;
    while(st)
    {
        int kk=((unsigned int)x&((unsigned int)1<<st-1))>>(st-1);
        if(ch[now][!kk]) {
            now=ch[now][!kk],sum=sum+((unsigned int)1<<st-1);
        }
        else {
            now=ch[now][kk];
        }
        st--;    
    }
    return sum;
}
void puts(ui x)
{
    ui st=31;
    int now=1;
    while(st)
    {
        int kk=((unsigned int)x&((unsigned int)1<<st-1))>>(st-1);
        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=1;i<=n-1;i++)
    {
        scanf("%lld%lld%lld",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    dfs(1,0,-1);
    for(int i=1;i<=n;i++)
     ans=max(ans,dis[i]);//warning!!
    puts(dis[2]);
    for(int i=3;i<=n;i++)
    {
        ans=max(ans,work(dis[i]));
        puts(dis[i]);
    }
    printf("%lld",ans);
    return 0;
}
洛谷4551

 

posted @ 2018-06-07 23:18  *Miracle*  阅读(713)  评论(1编辑  收藏  举报