数据结构 01trie树【字典树】

trie树

trie树是一种高效存储和查询字符串集合的数据结构

概念:

【算法竞赛进阶指南】:Tire(字典树)是一种用于实现字符串快速检索的多叉树结构。Trie的每个节点都拥有若干个字符指针(是trie的第二维,记录的根节点的所有儿子节点(互为兄弟节点),若在插入或检索时扫描到一个字符c,就沿着当前节点的c字符指针,走向该指针指向的节点。

过程:

0号节点即是根节点,又是空节点。

插入:

  令指针p起初指向根节点,然后依次扫描字符串S中的每个字符c

  1.将字符c映射一下

  2.若当前字符c不存在,则新开辟一个节点(++ idx),令p根节点的儿子节点等于idx【也就是字符c的指针指向idx】

  3.让p=c的指针

查询:

  令指针p起初指向根节点,然后依次扫描字符串S的每个字符c

  1.将字符c映射一下
  2.若p的c字符指向空,则说明S中没有被插入到trie树种,结束检索

  3.若p的c字符指针指向一个已经存在的节点Q,则令p=Q。

  4.当S中的字符扫描完毕时,若当前节点p被标记为一个字符串的末尾,则说明Trie中存在,否则说明S没有插入过Trie  

Trie字符串统计

题目:

维护一个字符串集合,支持两种操作:

I x 向集合中插入一个字符串 x;
Q x 询问一个字符串在集合中出现了多少次。
共有 N 个操作,输入的字符串总长度不超过 105,字符串仅包含小写英文字母。

输入格式
第一行包含整数 N,表示操作数。

接下来 N 行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。

输出格式
对于每个询问指令 Q x,都要输出一个整数作为结果,表示 x 在集合中出现的次数。

每个结果占一行。

数据范围
1≤N≤2∗104
输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab
输出样例:
1
0
1 

代码:

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

using namespace std;

const int N = 100010;

int n;
char s[N], op[2];
int tr[N][26], cnt[N], idx;

void insert(char s[])
{
    int len = strlen(s), p = 0;
    for (int i = 0; i < len; i ++ )
    {
        int t = s[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++ idx;
        p = tr[p][t];
    }
    cnt[p] ++;
}

int query(char s[])
{
    int len = strlen(s), p = 0;
    for (int i = 0; i < len; i ++ )
    {
        int t = s[i] - 'a';
        if (!tr[p][t]) return 0;
        p = tr[p][t];
    }
    return cnt[p];
}
int main()
{
    cin >> n;
    while (n -- )
    {
        scanf("%s", op);
        scanf("%s", s);
        if (*op == 'I')
        {
            insert(s);
        }
        else 
        {
            printf("%d\n", query(s));
        }
    }
    return 0;
}

最大异或对

题目:

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

输入格式
第一行输入一个整数 N。

第二行输入 N 个整数 A1~AN。

输出格式
输出一个整数表示答案。

数据范围
1≤N≤105,
0≤Ai<231
输入样例:
3
1 2 3
输出样例:
3

代码:

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

using namespace std;

const int N = 3100010;

int n;
int tr[N][2], idx;
//第二维记录的是根节点的儿子的兄弟节点。

void insert(int x)
{
    // cout << x << endl;
    int p = 0;
    for (int i = 31; i >= 0; i --)
    {
        int u = (x >> i & 1);
        if (!tr[p][u]) tr[p][u] = ++ idx;
        p = tr[p][u];
    }
}

int query(int x)
{
    // cout << x << endl;
    int p = 0, ans = 0;
    for (int i = 31; i >= 0; i -- )
    {
        int u = (x >> i & 1) ^ 1;
        if (tr[p][u])
        {
            ans += 1 << i;
            p = tr[p][u];
        }
        else p = tr[p][!u];
    }
    return ans;
}
int main()
{
    cin >> n;
    int res = 0;
    while (n -- )
    {
        int x; scanf("%d", &x);
        insert(x);
        res = max(res, query(x));
    }
    
    cout << res << endl;
    
    return 0;
}

牛异或

题目:

农夫约翰在给他的奶牛们喂食时遇到了一个问题。

他共有 N 头奶牛,编号 1∼N。

每次喂食前,这 N 头奶牛会按照 1∼N 的顺序站成一排。

此外,每头奶牛都被分配了一个可能不唯一的整数。

那么所有被分配的整数就形成了一个长度为 N 的整数序列。

请你在该整数序列中找出一个连续的非空子序列,使得子序列中元素的异或和能够最大。

如果存在多个这样的序列,那么选择序列末端整数对应的奶牛编号更小的那个序列。

如果仍然存在多个可选的序列,那么选择长度最短的那个序列。

输入格式
第一行包含整数 N。

第 2∼N+1 行,每行包含一个整数,其中第 i 行的整数表示编号为 i−1 的牛被分配的整数值。

输出格式
输出三个整数,分别表示最大的异或和,所选序列首端整数对应的奶牛编号,所选序列末端整数对应的奶牛编号。

数据范围
1≤N≤105,
分配给奶牛的整数的范围是 [0,221−1]。

输入样例:
5
1
0
5
4
2
输出样例:
6 4 5

代码:

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

using namespace std;

const int N = 100010;

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

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

int query(int x)
{
    int p = 0;
    for (int i = 21; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (tr[p][!u]) p = tr[p][!u];
        else p = tr[p][u];
    }
    return id[p];
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n ;i ++ )
    {
        scanf("%d", &s[i]);
        s[i] ^= s[i - 1];
    }
    
    int ans = -1, l = 1, r = 1;
    for (int i = 1; i <= n; i ++ )
    {
        // insert(s[i], i);
        int k = query(s[i]);
        int res = s[i] ^ s[k];
        if (ans < res) {
            ans = res;
            l = k + 1;//如果存在s[L1 ~ R] == s[L2 ~ R],且L1<L2,在insert的时候会被覆盖掉的,所以不用考虑第三个情况,会自动保留最短
            r = i;
        }
        insert(s[i], i);
    }
    
    cout << ans << ' ' << l << ' ' << r << endl;
    
    return 0;
}

最大异或和

题目:

给定一个非负整数数列 a,初始长度为 N。

请在所有长度不超过 M 的连续子数组中,找出子数组异或和的最大值。

子数组的异或和即为子数组中所有元素按位异或得到的结果。

注意:子数组可以为空。

输入格式
第一行包含两个整数 N,M。

第二行包含 N 个整数,其中第 i 个为 ai。

输出格式
输出可以得到的子数组异或和的最大值。

数据范围
对于 20% 的数据,1≤M≤N≤100
对于 50% 的数据,1≤M≤N≤1000
对于 100% 的数据,1≤M≤N≤105,0≤ai≤231−1
输入样例:
3 2
1 2 4
输出样例:
6 

代码:

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

using namespace std;

const int N = 100010 * 31, M = 100010;

int n, m;
int s[M];
int tr[N][2], cnt[N], idx;

void insert(int x, int v)
{
    int p = 0;
    for (int i = 31; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (!tr[p][u]) tr[p][u] = ++ idx;
        p = tr[p][u];
        cnt[p] += v;
    }
}

int query(int x)
{
    int res = 0, p = 0;
    for (int i = 31; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (cnt[tr[p][!u]]) p =tr[p][!u], res = res * 2 + 1;
        else p = tr[p][u], res = 2 * res;
    }
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        int x; scanf("%d", &x);
        s[i] = s[i - 1] ^ x;
    }
    
    int res = 0;
    insert(s[0], 1);
    
    for (int i = 1; i <= n; i ++ )
    {
        if (i > m) insert(s[i - m - 1], -1);
        res = max(res, query(s[i]));
        insert(s[i], 1);
    }
    
    cout << res << endl;
    
    return 0;
}

  

 

posted @ 2021-05-19 21:20  rookie161  阅读(134)  评论(0编辑  收藏  举报