Trie树小结

说来惭愧,一个月前就写了trie的题,现在才想起来整理博客,中途我会隔一段时间在敲一下原来做过的题,能做到速度和质量后再来写一下我的一些心得吧;

Trie树:一种基本的数据结构;

用来实现字符串快速检索的一种多叉树,每个节点拥有多个字符指针,若在插入或检索中扫描到一个字符c,沿着当前指针走向指针所指的下一个节点;

1.初始化:只有一个根节点,字符指针均指向空;

2.插入:插入一个字符串s;

首先建一个指针P指向根节点,扫描每一个字符c,若P的c字符指针指向节点Q,那么令P=Q;

如果P的c字符指针指向空,那么新建一个节点Q,令P=Q

依次扫描,直至在字符串的末尾(当前的P节点)标记一下;

3,查询:查过一个字符串S是否在tire中出现过;

从根节点出发,扫描S的每一个字符c,如果当前指针P的c字符指针指向空,那么结束,S没有出现过;

如果扫描完S的每一个字符后,当前指针P被标记过,那么S出现过,否则没有出现过;

以上是trie的基本操作;

此类问题也常与前缀问题,异或问题结合,下面会接着讲;

加几道基础题:

1.秘密消息Secret Message

题目大意:给定M个01串,N个01串,对于每一个N串,求满足有多少个M个串和它拥有相同的前缀,这个前缀长度必须等于N串和M串长度的的较小者;

思路:我们对M个串建一个trie树,用pass[i]表示经过节点i的M串有多少个,用c[i]表示用i结尾的字符串的个数;

那么对于query函数,我们只需要依次遍历N串的字符,用c[i]更新累加ans,只有两种情况:

1.如果指向空节点,返回答案ans;//因为当前字符串N的长度比路径上的字符串长,ans等于遍历的路径上的字符串总数;

2.这个串包括这个路径上的所有字符,返回ans+pass[i]-c[i];//画图理解一下;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
    x*=f;
}

int n,m,x,idx,a[500000],trie[500000][3],pass[500000],c[500000];

inline void insert(int k) {
    int p=0;
    for(int i=1;i<=k;i++) {
        int ch=a[i];
        if(!trie[p][ch]) trie[p][ch]=++idx;
        p=trie[p][ch];
        pass[p]++;
    }
    c[p]++;
}

inline int query(int k) {
    int p=0,ans=0;
    for(int i=1;i<=k;i++) {
        int ch=a[i];
        if(!trie[p][ch]) return ans;
        p=trie[p][ch];
        ans+=c[p];
    }
    return ans+=pass[p]-c[p];
}

int main() {
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        read(x);
        for(int j=1;j<=x;j++) 
            read(a[j]);
        insert(x);
    }    
    for(int i=1;i<=m;i++) {
        read(x);
        for(int j=1;j<=x;j++)
            read(a[j]);
        cout<<query(x)<<endl;
    }
    return 0;
}
View Code

2.L语言:

给定N个单词和M个句子,问这M个句子中包括这N个单词的最长前缀是多少;

思路:

我们对于N单词建一个trie树,对单词结尾打上标识;

对于每一个句子,我们用f[i]表示前i个字符是否可行,那么每次检索[i+1,L]//L指句子的长度;如果遍历到有标记的节点,更新f[i];

#include<bits/stdc++.h>
using namespace std;

template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
    x*=f;
}

int n,m,idx,l,c[5000000],f[5000000],trie[5000000][26];
char ch[1500000];

inline void insert(char *s) {
    int p=0,l=strlen(s+1);
    for(int i=1;i<=l;i++) {
        int ch=s[i]-'a';
        if(!trie[p][ch]) trie[p][ch]=++idx;
        p=trie[p][ch];
    } 
    c[p]=1;
}

inline int query(char *s,int st) {
    int p=0;
    for(int i=st;i<=l;i++) {
        int ch=s[i]-'a';
        if(!trie[p][ch]) return 0;
        p=trie[p][ch];
        if(c[p]) f[i]=1;
    }
    //return c[p];
}

int main() {
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        scanf("%s",ch+1);
        insert(ch);
    }
    for(int k=1;k<=m;k++) {
        scanf("%s",ch+1);
        l=strlen(ch+1);
        f[0]=1;
        for(int i=1;i<=l;i++)
            f[i]=0;
        for(int i=0;i<=l;i++) 
            if(f[i]) query(ch,i+1);
        for(int i=l;i>=0;i--)
            if(f[i]) {
                cout<<i<<endl;
                break;
            }
    }
    return 0;
} 
View Code

3.Phone list

题目大意:给出 N 个字符串,看是否有一个串 A 为串 B 的前缀。

因为我们在Trie树插入操作中已经对每一串字符串都打上了标记,
所以满足答案的关系只有两种,
一种是插入的串是之前的串的前缀,那么只要看在插入过程中是不是没有新增节点;
另一种是插入的串包括之前的串,也就是之前的串是插入的串的前缀,这个只要看看有没有走到打了标记的点就好了。
所以只需要在插入时判断即可;

请不要忘记清空trie;

#include<bits/stdc++.h>
using namespace std;

int T,n,m,idx,ans=0,trie[100050][25],cnt[100050];
char str[25];

template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}

inline void clear() {
    for(int i=0;i<=idx;i++) {
        for(int j=0;j<=15;j++) {
            trie[i][j]=0,cnt[i]=0;
        }
    }
}

inline int insert(char str[]) {
    int p=0,l=strlen(str+1);
    int f1=0,f2=1;
    for(int i=1;i<=l;i++) {
        int ch=str[i]-'0';
        if(!trie[p][ch]) trie[p][ch]=++idx,f2=0;
        p=trie[p][ch];
        if(cnt[p]) f1=1;
    }
    cnt[p]=1;
    if(f1) return 1;
    if(f2) return 1;
    return 0;
}

int main() {
    read(T);
    while(T--) {
        clear();
        ans=0;idx=0;
        read(n);
        for(int i=1;i<=n;i++) {
            scanf("%s",str+1);
            if(insert(str)) ans=1;
        }
        if(ans) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
    return 0;
}
View Code

以下是算法进阶指南上的题目:

1.前缀统计

题目描述:

给定N个字符串S1,S2SN,接下来进行M次询问,每次询问给定一个字符串T,求S1SN中有多少个字符串是T的前缀。

输入格式

第一行输入两个整数N,M。

接下来N行每行输入一个字符串Si

接下来M行每行一个字符串T用以询问。

输出格式

对于每个询问,输出一个整数表示答案。

每个答案占一行。

思路:

我们将N个字符串插入trie树中,但是我们为了处理重复现象,我们将每个节点记录为有多少个字符串的在这里结尾,记录个数cnt,而不能只做结尾标记;

检索字符串T,累加路径上的cnt即可;

#include<bits/stdc++.h>

using namespace std;

#define N 1000010

int idx,n,m;
int tire[N][26],cnt[N];
string s;

template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
    x*=f;
}

inline void insert(string s) {
    int p=0;
    for(int i=0;i<s.size();i++) {
        int ch=s[i]-'a';
        if(tire[p][ch]==0) tire[p][ch]=++idx;
        p=tire[p][ch];
    }
    cnt[p]++;
}

inline int query(string s) {
    int p=0,res=0;
    for(int i=0;i<s.size();i++)   {
        p=tire[p][s[i]-'a'];
        if(p==0) return res;
        res+=cnt[p];
    }
    return res;
}

int main() {
    read(n);read(m);
    for(int i=0;i<n;i++) {
        cin>>s;
        insert(s);
    }
    for(int i=0;i<m;i++) {
        cin>>s;
        cout<<query(s)<<endl;
    }
    return 0;
}
View Code

2.The XOR Largest Pair

题目描述:

在给定的N个整数A1A2ANA1,A2……AN中选出两个进行xor(异或)运算,得到的结果最大是多少?

输入格式

第一行输入一个整数N。

第二行输入N个整数A1A1~ANAN。

输出格式

输出一个整数表示答案。

数据范围

1N105
0Ai<231

思路:

枚举是行不通了;

根据XOR的性质是“相同为0,不同为1”,我们再选两个数异或时尽量选择每位不同的,尽可能的让结果大;

所以我们将每个数看做一个二进制数插入trie树中;

接下来对应每一个Ai,我们进行检索;

怎么检索呢,我们从根节点出发,对于当前Ai的j位,我们每次都尝试沿着与j位上数字的不同的指针向下检索,(这样答案尽可能大),

如果与其不同的指针指向空,那么我么只好沿着相同的走;

这样我们就找到了与当前Ai做XOR最大的值了;

#include<bits/stdc++.h>
using namespace std;
#define N 1000100
#define M 3000000
int n,a[N],tire[M][2],idx;
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
    x*=f;
}
inline void insert(int x) {
    int p=0;
    for(int i=31;~i;i--) {
        int ch=x>>i&1;
        if(tire[p][ch]==0) tire[p][ch]=++idx;
        p=tire[p][ch];
    }
}
inline int query(int x) {
    int p=0,res=0;
    for(int i=31;~i;i--) {
        int ch=x>>i&1;
        if(tire[p][ch^1]) {
            p=tire[p][ch^1];
            res+=1<<i;
        }
        else p=tire[p][ch];
    }
    return res;
}
int main() {
    read(n);
    for(int i=1;i<=n;i++) {
        read(a[i]);
        insert(a[i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,query(a[i]));
    cout<<ans<<endl;
}
View Code

3.The XOR Longest Path

洛谷4551

题目描述:

给定一个树,树上的边都具有权值。

树中一条路径的异或长度被定义为路径上所有边的权值的异或和:

formula.png

⊕ 为异或符号。

给定上述的具有n个节点的树,你能找到异或长度最大的路径吗?

输入格式

第一行包含整数n,表示树的节点数目。

接下来n-1行,每行包括三个整数u,v,w,表示节点u和节点v之间有一条边权重为w。

输出格式

输出一个整数,表示异或长度最大的路径的最大异或和。

数据范围

1n100000
0u,v<n
0w<231

思路:

设D[x]为根节点到x的路径上所有边权的XOR值;

那么有:D[x]=D[father[x]]xorWeight(x,father(x));

根据上式,我们可以先dfs求出当前所有节点的D[x]值,那么x到y的路径就是D[x]xorD[y],(即使没有经过根节点,那么重复的相同路径异或后为0,所以将相当于没有经过);

所以问题转化:

从D[1]~D[N]中选择两个使得XOR值最大,和上题类似;

同样使用trie树快速求解:

但是洛谷和Acwing的根节点不同,以下是Acwing的AC代码;

#include<bits/stdc++.h>
using namespace std;
#define N 500010
#define M 3000010
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
    x*=f;
}
int n,x,y,z,tot,idx,lin[N],d[M],tire[M][2];
struct gg{
    int y,next,v;
}a[N<<1];
inline void add(int x,int y,int v) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    a[tot].v=v;
    lin[x]=tot;
}
inline void dfs(int x,int fa,int sum) {
    d[x]=sum;
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y!=fa) {
            dfs(y,x,sum^a[i].v);
        }
    }
}
inline void insert(int x) {
    int p=0;
    for(int i=31;~i;i--) {
        int ch=x>>i&1;
        if(tire[p][ch]==0) tire[p][ch]=++idx;
        p=tire[p][ch];
    }
}
inline int query(int x) {
    int p=0,res=0;
    for(int i=31;~i;i--) {
        int ch=x>>i&1;
        if(tire[p][ch^1]) {
            p=tire[p][ch^1];
            res+=1<<i;
        }
        else p=tire[p][ch];
    }
    return res;
}
int main() {
    read(n);
    for(int i=1;i<n;i++) {
        read(x);read(y);read(z);
        add(x,y,z);
        add(y,x,z);
    }
    dfs(0,-1,0);
    for(int i=1;i<=n;i++) {
        insert(d[i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,query(d[i]));
    printf("%d
",ans);
    return 0;
}
View Code

ok撒~;(^_−)☆

原文地址:https://www.cnblogs.com/Tyouchie/p/11009008.html