Trie

835. Trie字符串统计

模板题:

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

  1. I x 向集合中插入一个字符串 x;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N 个操作,所有输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。

输入格式

第一行包含整数 N,表示操作数。

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

输出格式

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

每个结果占一行。

数据范围

1≤N≤2∗10^4

输入样例:

5
I abc
Q abc
Q ab
I ab
Q ab

输出样例:

1
0
1

个人理解

个人感觉这个Trie是比较简单理解的,直接给上注意点还有图解;

AC代码:

#include <iostream>
using namespace std;
//定义:不超过 10^5;
//符合要求即可;
const int Ma=100010;
//这里的26是a~z(字符串仅包含小写英文字母)
//con[Ma]是记录Trie每个父节点最后一个的子节点(有图解);
//idx是记录c的“下标”;
int Trie[Ma][26],con[Ma],idx;   //这里容易误解的点:26 a~z并不是层级;而是以a~z带头的父节点;
char str[Ma];
//这是构造Trie树;
void insert(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int c=str[i]-'a';
        //如果没有记录这个字符,idx开辟一个位置来兜住;
        if(!Trie[p][c]){
            Trie[p][c]=++idx;
        }
        //p来指向下一个;
        p=Trie[p][c];
    }
    //记录最后一个字符的位置的个数; 比如 abcd 记录一次,abcd 再来一次 con[p]=2;
    con[p]++;
}
//查找
int search(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int c=str[i]-'a';
        //按顺序找下去,不对直接返回0;
        if(!Trie[p][c]){
            return 0;
        }else{
            //指向下一个;
            p=Trie[p][c];
        }
    }
    //返回结果;
    return con[p];
}
int main(){
    int n;
    cin >> n;
    while(n--){
        char c;
        cin >> c >> str;
        if(c=='I'){
           insert(str);
        }else{
            cout << search(str) << endl;
        }
    }
    return 0;
}

图解:来源--------AC 四谷夕雨

Trie2.PNG

解疑:

Trie[p][c]=++idx; //这是什么?idx干什么用的?    
  • 一维下标:父节点的位置(层级)
    二维下标:当前节点的位置(az->025)
    值:当前节点的id(用idx标识,唯一性)
  • 这里是可以自己图画手推的:手推也可能就会发现用这种方法的话,是会浪费一层里的空间的.......这就自定义想了,在下面我会给出一些例题,是很明显的吧,有优化可以q我
  • 自己手推很重要,本人在没手推之前,总是觉得字符之间会重复什么的.......,手推才意识到idx的强大,idx是即将待操作的结点下标 (看自己的理解吧);
  • 哦对,在这以前要知道怎么构建Trie,要不然够呛,比如abcd 的“路径”是可以保存 abc的只需记录最后一个节点;

相关例题:

AC. 最大异或对:

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

输入格式

第一行输入一个整数 N。

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

输出格式

输出一个整数表示答案。

数据范围

1≤N≤10^5
0≤Ai<2^31

输入样例:

3
1 2 3

输出样例:

3

解析:

  • 异或对:随便两个数进行逻辑异或操作求出两两匹配之间最大的异或值;

  • 逻辑异或操作: 异或(XOR)

    逻辑异或运算,运算规则:相异为一,相同为零。即两个操作数不一样时结果为1,两个操作数相同时结果为0。(这都是二进制的形式比较)

    操作数1 操作数2 结果值
    1 1 0
    1 0 1
    0 1 1
    0 0 0

AC代码(暴力):

#include<iostream>
using namespace std;
int sum[100005],a[100005];
int main(){
    int n,ans=0;
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        //前缀和;
        sum[i]=sum[i-1]^a[i];
    }
    //两两比较;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            ans=max(ans,sum[i]^sum[j-1]);
        }
    }
    cout << ans << endl;
    return 0;
}
  • 暴力的做法是:第一个循环时间复杂度O(n),第二个O(N^2),时间复杂度肯定是爆掉的;在下面用Trie优化一下

AC代码(Trie):

#include <iostream>
using namespace std;
//0≤Ai<2^31所以最高位是第30位。
//因为是正数,所以要保证第一位是1
const int Ma=3100010,N=1e5+10;
#define int long long
int e[Ma][2],idx;
int a[N];
//构建Trie;
void insert(int x){
    int p=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(!e[p][u]) e[p][u]=++idx;
        p=e[p][u];
    }
}
int search(int x){
    int p=0,res=0;
    for(int i=30;i>=0;i--){
        //捋明白位移情况在配合上述的Trie手推就很好理解了
        int u=x >> i & 1;
        //判断同层有没有相反的数,只有1或者0,1取反找0,0取反找1;没有走原路;(建议画图理解)
        if(e[p][!u]){
            p=e[p][!u];
            res=res*2+1;  //这里跟转10进制一样的0*10+n
        }
        else{
            //走原路,不要把取反的值带过来看了......
            p=e[p][u];
            res=res*2+0;
        }
    }
    return res;
}
signed main(){
    int n;
    cin >> n;
    if(n==1){int x;cin >> x;cout << x << endl;return 0;}
    for(int i=0;i<n;i++){
        cin >> a[i];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++){
        res=max(res,search(a[i]));
    }
    cout << res << '\n';
}

总结:手推, 手推 , 还是手推;

再分享一道马蹄的题:

MT2055最大异或和:

给定一串手链,每个珠子被赋予一个价值wi,现要从中截取连续的一段,使得他们的异或和最大(注意,手链还未串上)。

格式

输入格式:

第11行包含一个正整数N;
第22行n个正整数wi​,表示珠子价格。

输出格式:

一个正整数,输出最大异或和。

样例 1

输入:

5
1 2 3 4 5

输出:

7
备注

其中:n≤2000,wi≤100000

早期代码: 前缀和+暴力

#include<iostream>
using namespace std;
int sum[100005],a[100005];
int main(){
    int n,ans=0;
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        //前缀和;
        sum[i]=sum[i-1]^a[i];
    }
    //第一个和每一个进行比较;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            ans=max(ans,sum[i]^sum[j-1]);
        }
    }
    cout << ans << endl;
    return 0;
}

后期的优化: 前缀和+Trie

#include <iostream>
using namespace std;
const int Ma=3100010,N=1e5+10;
#define int long long
int e[Ma][2],idx;
int a[N];
void insert(int x){
    int p=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(!e[p][u]) e[p][u]=++idx;
        p=e[p][u];
    }
}
int search(int x){
    int p=0,res=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(e[p][!u]){
            p=e[p][!u];
            res=res*2+1;
        }
        else{
            p=e[p][u];
            res=res*2+0;
        }
    }
    return res;
}
signed main(){
    int n;
    cin >> n;
    if(n==1){int x;cin >> x;cout << x << endl;return 0;}
    for(int i=0;i<n;i++){
        cin >> a[i];
        //前缀和
        a[i]^=a[i-1];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++){
        res=max(res,search(a[i]));
    }
    cout << res << '\n';
}
  • 这题是跟上述不同的:这个是实现前缀和的异或相加,在进行异或对操作;
posted @ 2024-10-06 12:11  小码king  阅读(29)  评论(0)    收藏  举报