zoukankan      html  css  js  c++  java
  • 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撒~;(^_−)☆

  • 相关阅读:
    excel表格的jar包
    mac和jar的关系
    BAT批处理文件入门-笔记
    没看完_perl的find模块很全面的讲解!!
    perl怎么拷贝一个文件到另一个文件夹中或者怎么拷贝文件夹到另一个文件夹
    perl 中用到的-*的具体解释,方便查看
    perl语言的笔记--啥是mkpath
    剑指哦佛_我的第一篇博客,哦耶
    ajax创建万能的XmlHttpRequest对象
    动态实例化窗体
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/11009008.html
Copyright © 2011-2022 走看看