Trie树(字典树)

目录​​​​​​​

介绍:

Trie树

例题:

例题一  :

例题二  :

例题三 :


介绍:

Trie树

数据结构:可以高效的存储和查找字符串的

题目中需要用到Trie树时的关键词:全是大写字母,全是小写字母,全是数字,全是0/1

两个应用:

1.是否存在一个串,是当前串的前缀:当前串遍历路径中,是否存在串结尾标志,如果存在,成立

2.当前串,是否是某一个串的前缀:用当前串遍历路径中,是否创建过新节点,如果没有,成立

根节点:在Trie树中,下标为0的点,既是根节点,又是空节点,所以节点下表从1开始,++idx

空间大小:一般trie树的节点数要多开,具体根据题目分析


例题:

例题一  :

142. 前缀统计 - AcWing题库

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5e5 + 7, M = 1e6 + 7;

char s[M];
int trie[N][26], cut[N], idx;	//trie保存前缀树的信息, cut保存以某个节点作结的前缀的数量,  index记录节点号,注意index是关键字
int m, n;

void insert()
{
    int p = 0;
    for (int i = 0; s[i]; i ++ )	//一种巧妙的读取字符的方法 
    {
        int t = s[i] - 'a';
        if(!trie[p][t])
            trie[p][t] = ++ idx;
        p = trie[p][t];
    }
    cut[p] ++;
}

bool exist_query()  //判断一个串是否存在
{
    int p = 0;
    for(int i = 0; s[i]; i ++ )
    {
        int t = s[i] - 'a';
        if(!trie[p][t])
            return false;
        p = trie[p][t];
    }
    return true;
}

int num_query() //查找一个串的前缀数量
{
    int res = 0, p = 0;
    for(int i = 0; s[i]; i ++ )
    {
        int t = s[i] - 'a';
        if(!trie[p][t])
            break;  //不能用return 0,这里是结束查找而不是没找到的意思,没找到的话 res = 0 
        p = trie[p][t];
        res += cut[p];
    }
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    while (n -- )
    {
        scanf("%s", s);
        insert();	//因为 s 是全局变量,所以这里可以不用传递 s 的值就可以直接调用 
    }
    
    while (m -- )
    {
        scanf("%s", s);
        printf("%d\n", num_query());
    }
    
    return 0;
}

 tip:1.一种遍历字符串的方法

        2.为什么 insert()和 query()不需要传递实参

参考:字典树(前缀树)


例题二  :

 上海理工大学校内选拔赛

这道题不仅仅需要插入和查找,还用到了删除操作,但其实删除和插入大同小异

因为本题目数的大小<=1e5,所以我们可以规定每一个数都是6为的,不足6位前面补0

那么这个trie树的就是一棵“满二叉树”,所有叶子节点都在同一层,这样就方便计算了

不同与上一题,这一题的cnt在循环的里面++或者--,这是因为这里的cnt含义是不同的!千万不要思维定式了,上一题的cnt是某个字符串的数量,而这里的cnt是某个该节点代表的值的数量,例如插入两个111111,111111,那么cnt[1]~cnt[6]=2,表示该节点插入了两次,之所以这样,是方便下面的删除操作,我们只需要直接让cnt[k]--,就代表这个点代表的值少了一个,从而实现了删除的效果

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = 2000010;

int n, a[N], cnt[N];
int tr[M][20], idx;

void Insert(int x)
{
    string str = to_string(x);
    while(str.size() < 6)   str = "0" + str;
    int p = 0;
    for(int i = 0; str[i]; i ++ )
    {
        int u = str[i] - '0';
        if(!tr[p][u])   tr[p][u] = ++ idx;
        p = tr[p][u];
        cnt[p] ++ ;//??
    }
}

void erase(int x)
{
    string str = to_string(x);
    while(str.size() < 6)   str = '0' + str;
    int p = 0;
    for(int i = 0; str[i]; i ++ )
    {
        int u = str[i] - '0';
        p = tr[p][u];
        cnt[p] -- ;
    }
}

int query(int x)
{
    string str = to_string(x);
    while(str.size() < 6)   str = "0" + str;
    int p = 0, ans = 0;
    for(int i = 0; str[i]; i ++ )
    {
        int u = 9 - (str[i] - '0');
        while(1)
        {
            if(cnt[tr[p][u]])
            {
                p = tr[p][u];
                ans = ans * 10 + (u + str[i] - '0') % 10;
                break;
            }
            u -- ;
            if(u < 0)   u = 9;
        }
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    
    cin >> n;
    for(int i = 0; i < n; i ++ )
    {
        cin >> a[i];
        Insert(a[i]);
    }
    
    for(int i = 0; i < n; i ++ )
    {
        erase(a[i]);
        int res = query(a[i]);
        cout << res << " ";
        Insert(a[i]);
    }
    cout << endl;
    
    
    return 0;
}


例题三 :

1414. 牛异或 - AcWing题库

 Trie + 前缀和的模板题

题目要求我们找到一个异或最大的区间,我们可以前缀和预处理,这样每次只需要计算两个区间,然后枚举每一个右端点,这样就确保了异或和最大的情况下,右端点最小

题目还要求在右端点最小的情况下,区间长度最小,即左端点最大,那么对于预处理前缀和之后的左端点,我们只需要让后出现的左端点覆盖前面相同的左端点就可以了,这样就实现了我们每次取得都是最靠右的左端点

其次,本题不能使用#define int long long,因为在trie题目中,数组范围本身就比较大,我们在开long long,又大了一倍,很容易Memory Limit

另外我们采取的是先查询在插入的方法,所以在查询之前要插入一个0,表示L-1可以为0

#include <iostream>
#include <cstring>
#include <algorithm>


#define endl '\n'
#define debug(x)    cout << #x << " = " << endl

using namespace std;

const int N = 100010;


int n, w[N], id[N * 21];
int tr[N * 21][21], idx;

void Insert(int x, int y)
{
    int p = 0;
    for(int i = 20; i >= 0; i -- )
    {
        int u = (x >> i & 1);
        if(!tr[p][u])   tr[p][u] = ++ idx;
        p = tr[p][u];
    }
    id[p] = y;
}

int query(int x)
{
    int p = 0;
    for(int i = 20; i >= 0; i -- )
    {
        int u = (x >> i & 1);
        if(tr[p][!u])   p = tr[p][!u];
        else p = tr[p][u];
    }
    return id[p];
}

signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        int x;  cin >> x;
        w[i] = w[i - 1] ^ x;
    }
    
    int res = -1, l = 0, r = 0;
    
    Insert(w[0], 0);
    for(int i = 1; i <= n; i ++ )
    {
        int j = query(w[i]);
        if((w[j] ^ w[i]) > res) 
        {
            res = (w[j] ^ w[i]);
            r = i;
            l = j + 1;
        }
        Insert(w[i], i);
    }
    
    cout << res << " " << l << " " << r << endl;
    
    return 0;
}

posted @ 2022-05-05 08:42  光風霽月  阅读(26)  评论(0编辑  收藏  举报