字符串——字典树模板及习题(持续更新)

字典树

字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。
字典树叫前缀树更容易理解。
字典树的样子
1

原理

一般来说trie树支持俩个操作:
1.Insert(w); 就是将字符串w插入到集合
2.Search(s);就是查询字符串s在不在集合中
2
如上如所示,插入的字符串包括 ’in’,‘inn’,‘int’,‘tea’,‘to’,‘ten’,这六个字符串。黑色表示是终结点,在插入一个字符串的结尾,要将该节点标记为终结点,及接下来的代码中的color[p] = 1。

查询一个字符串在不在集合中就是,相当于顺着trie找,当前字符不在时返回false,若存在就在该节点的子节点中找下一个字符,找到结尾时判断一下当前节点,如果是终结点,那么返回true,否则返回false。

模板(数组简单实现)

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int CHARSET = 26;
int trie[MAX_NODE][CHARSET];
int color[MAX_NODE] = {0};//标记是否为终结点
int k = 1;

void Insert(char *w){
    int len = strlen(w);
    int p = 0;
    for(int i = 0; i < len; i++){
        int c = w[i] - 'a';
        if(!trie[p][c]){
            trie[p][c] = k;
            k++;
        }
        p = trie[p][c];
    }
    color[p] = 1;
}

bool Search(char *s){
    int len = strlen(s);
    int p = 0;
    for(int i = 0; i < len; i++){
        int c = s[i] - 'a';
        if(!trie[p][c]){
            return false;
        }
        else{
            p = trie[p][c];
        }
    }
    return color[p] == 1;
}

int main()
{

}

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int CHARSET = 26;
const int maxn = 1e5+50;
int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];
char str[maxn], value[maxn][20], key[maxn][20];
int k = 1;

inline int idx(char a){
    return a - 'a';
}

void Insert(char *w, int v){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        p = trie[p][c];
    }
    mark[p] = v;
}

int Search(char *s){
    int p = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++){
        int c = idx(s[i]);
        if(!trie[p][c]){
            return 0;
        }
        p = trie[p][c];
    }
    return mark[p];
}

int main()
{
    int cur = 1;
    while(gets(str) && str[0] != '\0'){
        sscanf(str, "%s%s", value[cur], key[cur]);
        Insert(key[cur], cur);
        cur++;
    }
    while(scanf("%s", str) != EOF){
        int ans = Search(str);
        if(ans == 0){
            printf("eh\n");
        }
        else{
            printf("%s\n", value[ans]);
        }
    }
    return 0;
}

习题(持续更新)

1.UVA 644 Immediate Decodability

链接:UVA 644
题意:多组输入,每一组结尾用单字符‘9’表示该组结束。给你一系列由‘0’,‘1’构成的字符串,让你判断是否有字符串是其他字符串的前缀,若有输出Set 2 is not immediately decodable,否则输出Set 1 is immediately decodable。
题解:就是简单的运用一下trie树,在执行Insert的时候有两种情况表示为有字符串为其他字符串的前缀。
1.插入该字符串的最后一个字符的时候没有新键节点。
2.发现下一个字符处有结尾标签。

代码:

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e4+50;
const int CHARSET = 3;
const int maxn = 1e5+50;
int trie[MAX_NODE][CHARSET];
int color[MAX_NODE] = {0};
int k = 1;
char str[maxn];

int Insert(char *w){
    int len = strlen(w);
    //cout << len << endl;
    int p = 0;
    for(int i = 0; i < len; i++){
        int c = w[i] - '0';
        if(i == len-1){//情况1
            if(trie[p][c]){
                return 0;
            }
        }
        if(trie[p][c] == 0){
            trie[p][c] = k;
            k++;
        }
        if(color[trie[p][c]] == 1){//情况2
            return 0;
        }
        p = trie[p][c];
    }
    color[p] = 1;
    return 1;
}

int main()
{
    int flag = 1;
    int cur = 1;
    while(scanf("%s", str) != EOF){
        if(strlen(str) == 1 && str[0] == '9'){
            memset(color, 0, sizeof(color));
            fill(trie[0], trie[0] + MAX_NODE * CHARSET, 0);
            k = 1;
            if(flag){
                printf("Set %d is immediately decodable\n", cur++);
            }
            else{
                printf("Set %d is not immediately decodable\n", cur++);
            }
            flag = 1;
            continue;
        }
        if(!Insert(str)){
            flag = 0;
        }
    }
    return 0;
}

2.UVA 1127 Word Puzzles

链接UVA 1127
题意:T组, 每组包括n, m, t, 分别表示字符迷宫的行和列, t 表示接下来的字符串数量。接下来输入一个n*m的字符数组,以及t串字符串,全部由大写字母组成。现在让你在字符数组中找出这些字符串,寻找的方式是可以由一个字符出发沿着八个方向,A到H表示这八个方向,A表示North上, 按时钟顺时针。
按顺序输出找到每一个字符串的起始位置以及方向。
题解:将这写字符串用字典树存起来,然后暴力搜索即可,这里用到了字典树的前缀树的性质可以减少时间复杂度,所以要用字典树。
代码
有点小问题没找出来

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int maxn = 1e3+50;
const int CHARSET = 26;
int trie[MAX_NODE][CHARSET];
int val[MAX_NODE] = {0}; // 标记终结点,并且标记是第几个插入的。
char str[maxn];
int k = 1;

int T, n, m, t;
char mp[maxn][maxn];
int vis[MAX_NODE];
int ans[maxn][4];
int dir[][2] = {{-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}};
//从上开始顺时针的8个方向

inline int idx(char a){
    return a - 'A';
}

inline void clean(){
    memset(vis, 0, sizeof(vis));
    memset(val, 0, sizeof(val));
    memset(trie[0], 0, sizeof(trie)); //每一次必须清0
    k = 1;
}

void Insert(char *w, int v){
    int len = strlen(w);
    int p = 0;
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(!trie[p][c]){
            memset(trie[k], 0, sizeof(trie[k]));//每一次必须清0
            trie[p][c] = k++;
        }
        p = trie[p][c];
    }
    val[p] = v;
}

void bfs(int x, int y, int d){
    int p = 0;
    int tx = x, ty = y;
    while(x >= 0&&x < n&&y >= 0&&y < m){
        int c = idx(mp[x][y]);
        if(!trie[p][c]){
            break;
        }
        else{
            //cout << (char)(c+'A');
            p = trie[p][c];
        }
        if(val[p] != 0){
            if(!vis[val[p]]){
                ans[val[p]][0] = tx;
                ans[val[p]][1] = ty;
                ans[val[p]][2] = d;
            }
            vis[val[p]] = 1;
        }
        x += dir[d][0];
        y += dir[d][1];
    }
}

int main()
{
    scanf("%d", &T);
    while(T--){
        scanf("%d %d %d", &n, &m, &t);
        for(int i = 0; i < n; i++){
            scanf("%s", mp[i]);
        }
        for(int i = 1; i <= t; i++){
            scanf("%s", str);
            Insert(str, i);
        }
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                for(int k = 0; k < 8; k++){
                    bfs(i, j, k);
                    //cout << endl;
                }
            }
        }
        for(int i = 1; i <= t; i++){
            printf("%d %d %c\n", ans[i][0], ans[i][1], ans[i][2] + 'A');
        }
    }
    return 0;
}

参考代码(正解)


#include<bits/stdc++.h>
 
using namespace std;
 
const int maxnode=1e6+50;
const int sigma_size=26;
const int maxn=1e3+50;
 
char mapp[maxn][maxn],word[maxn][maxn];
int ans[maxn][3];
int t,l,c,w;
int visited[maxn];
int dir[][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
 
struct Trie{
    int ch[maxnode][sigma_size];
    int val[maxnode];
    int sz;
    void clear(){
        sz=1;
        memset(ch[0],0,sizeof(ch[0]));
        memset(val,0,sizeof(val));
    }
    int idx(char c){
        return c-'A';
    }
    void insert(char *s,int v){
        int u=0,n=strlen(s);
        for(int i=0;i<n;i++){
            int c=idx(s[i]);
            if(!ch[u][c]){
                memset(ch[sz],0,sizeof(ch[sz]));
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u=ch[u][c];
        }
        val[u]=v;
    }
    void find_prefixes(int x,int y,int d){
        int u=0;
        int xx=x,yy=y;
        while(xx>=0&&xx<l&&yy>=0&&yy<c){
            int c=idx(mapp[xx][yy]);
            if(!ch[u][c]){
                break;
            }else{
                u=ch[u][c];
            }
            if(val[u]){
                if(!visited[val[u]]){
                    ans[val[u]][0]=x;
                    ans[val[u]][1]=y;
                    ans[val[u]][2]=d;
                }
                visited[val[u]]=1;
            }
            xx+=dir[d][0];
            yy+=dir[d][1];
        }
    }
};
Trie trie;
 
int main()
{
    scanf("%d",&t);
    while(t--){
        memset(visited,0,sizeof(visited));
        scanf("%d%d%d",&l,&c,&w);
        for(int i=0;i<l;i++){
            scanf("%s",mapp[i]);
        }
        trie.clear();
        for(int i=1;i<=w;i++){
            scanf("%s",word[i]);
            trie.insert(word[i],i);
        }
        for(int i=0;i<l;i++){
            for(int j=0;j<c;j++){
                for(int k=0;k<8;k++){
                    trie.find_prefixes(i,j,k);
                }
            }
        }
        for(int i=1;i<=w;i++){
            printf("%d %d %c\n",ans[i][0],ans[i][1],ans[i][2]+'A');
        }
    }
    return 0;
}

3.UVA 10282 Babelfish

链接UVA10282
题意:输入最多包含100,000个字典条目,后跟空白行,然后输入最多100,000个字符串消息。 每个字典条目都是一行,包含英文单词后跟一个空格和外语单词。 在词典中,没有外来词出现多次。 字符串消息是一些外语单词序列,每行一个单词。 输入中的每个单词都是一个序列最多10个小写字母。现在要你输出每一个外语单词对应的英文单词,如果不存在就输出“eh”。
题解:字典树模板题,输入用gets 和 sscanf 判断一些。
代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int CHARSET = 26;
const int maxn = 1e5+50;
int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];
char str[maxn], value[maxn][20], key[maxn][20];
int k = 1;

inline int idx(char a){
    return a - 'a';
}

void Insert(char *w, int v){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        p = trie[p][c];
    }
    mark[p] = v;
}

int Search(char *s){
    int p = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++){
        int c = idx(s[i]);
        if(!trie[p][c]){
            return 0;
        }
        p = trie[p][c];
    }
    return mark[p];
}

int main()
{
    int cur = 1;
    while(gets(str) && str[0] != '\0'){
        sscanf(str, "%s%s", value[cur], key[cur]);
        Insert(key[cur], cur);
        cur++;
    }
    while(scanf("%s", str) != EOF){
        int ans = Search(str);
        if(ans == 0){
            printf("eh\n");
        }
        else{
            printf("%s\n", value[ans]);
        }
    }
    return 0;
}

4.UVA 11362 PhoneList

链接UVA11362
题意:与第一题差不多就是换了一个皮,字典树简单题。
题解:见第一题
代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e5+50;
const int CHARSET = 26;
const int maxn = 1e5+50;
int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE] = {0};//标记是否为终结点
int k = 1;

int T, n;
char str[maxn];

inline int idx(char a){
    return a - '0';
}

inline void clean(){
    memset(mark, 0, sizeof(mark));
    fill(trie[0], trie[0] + MAX_NODE*CHARSET, 0);
    k = 1;
}

int Insert(char *w){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(i == len-1){
            if(trie[p][c]){
                return 0;
            }
        }
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        if(mark[trie[p][c]]){
            return 0;
        }
        p = trie[p][c];
    }
    mark[p] = 1;
    return 1;
}

int main()
{
    scanf("%d", &T);
    while(T--){
        clean();
        int flag = 1;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){
            scanf("%s", str);
            if(!Insert(str)){
                flag = 0;
            }
        }
        if(flag){
            puts("YES");
        }
        else{
            puts("NO");
        }
    }
    return 0;
}

5.HDU 1251 统计难题

链接HDU1251
题意:Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).
题解:用字典树,每一次插入的时候用数组记录当前字符串前缀的访问次数,查询的时候直接返回,输出就行。
代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int CHARSET = 26;
const int maxn = 1e5+50;
int trie[MAX_NODE][CHARSET];
int num[MAX_NODE] = {0};//记录当前前缀的访问次数
int k = 1;

char str[maxn];

inline int idx(char a){
    return a - 'a';
}

void Insert(char *w){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        p = trie[p][c];
        num[p]++;
    }
}

int Search(char *s){
    int p = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++){
        int c = idx(s[i]);
        if(!trie[p][c])
            return 0;
        p = trie[p][c];
    }
    return num[p];
}

int main()
{
    while(gets(str) && str[0] != '\0'){
        Insert(str);
    }
    while(scanf("%s", str) != EOF){
        printf("%d\n", Search(str));
    }
    return 0;
}

6.POJ 2001 Shortest Prefixes

链接POJ2001
一开始在vjudge上写,发现UVALive评测有问题搞了调了半天代码结果不是代码问题,在poj上一发过了。我的大好时光/(ㄒoㄒ)/~~。
题意:就是给你一些字符串,现在让你对每一个字符串找出一个最短前缀(包括本身)让该前缀只有该字符串有,若不存在则输出字符串本身。
题解:放到字典树中,统计一下每一个前缀出现的次数,查找的时候第一个出现次数为1 的前缀就是ans,若找不到返回字符串结尾下标。
代码

//#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int MAX_NODE = 1e5+50;
const int CHARSET = 26;
const int maxn = 1e5+50;

int k = 1;
int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];

int T;
char str[maxn][50];

inline void clean(){
    memset(mark, 0, sizeof(mark));
    fill(trie[0], trie[0] + MAX_NODE*CHARSET, 0);
    k = 1;
}

inline int idx(char a){
    return a - 'a';
}

void Insert(char *w){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        mark[trie[p][c]]++;
        p = trie[p][c];
    }
}

int Search(char *s){
    int p = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++){
        int c = idx(s[i]);
        if(!trie[p][c]){
            return 0;
        }
        if(mark[trie[p][c]] == 1){
            return i;
        }
        else{
            if(i == len-1 && mark[trie[p][c]] != 0){
                return i;
            }
        }
        p = trie[p][c];
    }
    return 0;
}

int main()
{
    //scanf("%d", &T);
    //getchar();
    //getchar();
    //while(T--){
        int cur = 1;
        //while(gets(str[cur]) && str[cur][0] != '\0'){
        while(scanf("%s", str[cur]) != EOF){
            Insert(str[cur]);
            cur++;
        }
        for(int i = 1; i < cur; i++){
            int tmp = Search(str[i]);
            cout << str[i] << " ";
            for(int j = 0; j <= tmp; j++){
                cout << str[i][j];
            }
            cout << endl;
        }
    //}
    return 0;
}

7.HDU 5687 Problem C

链接HUD5687
题意
有三种操作:
1、insert : 往神奇字典中插入一个单词
2、delete: 在神奇字典中删除所有前缀等于给定字符串的单词
3、search: 查询是否在神奇字典中有一个字符串的前缀等于给定的字符串
对于每一个search 操作,如果在度熊的字典中存在给定的字符串为前缀的单词,则输出Yes 否则输出 No。
题解:用字典树,操作1就是插入记录每一个前缀出现的次数,操作2就是删除掉要删除的前缀的后面的所有节点就是memset一下,并且将路径上的所有前缀出现的次数减去该需要删除的前缀出现的次数,操作3就是判断一下该字符串前缀出现的次数为0输出No不然输出Yes。
代码

//#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int MAX_NODE = 2e6+50;
const int CHARSET = 26;
const int maxn = 1e6+50;

int k = 1;
int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];

int T;
char str[maxn], op[maxn], tmp[maxn];

inline void clean(){
  memset(mark, 0, sizeof(mark));
  fill(trie[0], trie[0] + MAX_NODE*CHARSET, 0);
  k = 1;
}

inline int idx(char a){
  return a - 'a';
}

void Insert(char *w){
  int p = 0;
  int len = strlen(w);
  for(int i = 0; i < len; i++){
      int c = idx(w[i]);
      if(!trie[p][c]){
          trie[p][c] = k++;
      }
      p = trie[p][c];
      mark[p]++;
  }
}

int Search(char *s){
  int p = 0;
  int len = strlen(s);
  for(int i = 0; i < len; i++){
      int c = idx(s[i]);
      if(!trie[p][c]){
          return 0;
      }
      p = trie[p][c];
  }
  return mark[p];
}

int Delete(char *t){
  int p = 0;
  int len = strlen(t);
  for(int i = 0; i < len; i++){
      int c = idx(t[i]);
      if(!trie[p][c]){
          return 0;
      }
      p = trie[p][c];
  }
  //修改该路径上的每一个前缀出现的次数
  int cur = mark[p];
  p = 0;
  for(int i = 0; i < len; i++){
      int c = idx(t[i]);
      if(!trie[p][c]){
          return 0;
      }
      p = trie[p][c];
      mark[p] -= cur;
  }
  //将指向删除前缀后面的节点清除
  memset(trie[p], 0, sizeof(trie[p]));
  return 1;
}

int main()
{
  scanf("%d", &T);
  while(T--){
      scanf("%s%s", op, str);
      if(op[0] == 'i'){
          Insert(str);
      }
      else if(op[0] == 's'){
          int flag = Search(str);
          if(flag)
              cout << "Yes" << endl;
          else
              cout << "No" << endl;
      }
      else if(op[0] == 'd'){
          Delete(str);
      }
  }
  return 0;
}



8.POJ 1816 Wild Words

链接POJ1816
题意:现在给你n个模式串和m个匹配串,模式串中有 ?和∗ ? 和 ∗ 两种字符,?可以匹配任意一种字符, ∗ ∗ 可以匹配任意个字符,问每种匹配串可以和之前哪些模式串匹配。
题解:我们先构建好字典树,对于每个匹配串实现find,find的时候用类似dfs的写法,如果当前节点的字符或者?存在,直接dfs下一个位置的字符,如果当前字符对应的位置有 ∗ ∗ 存在,那么就从匹配串之后的每一个位置进行往下dfs,因为可以匹配任意个字符。而dfs计数的条件是匹配串匹配结束而且达到模式串某个结尾标记。则在这个模式串上做标记。需要注意的细节是此时不能直接return(这里我就被坑了一下没有考虑好),因为有可能此时的字符串和剩下的匹配串还能匹配,类似模式串A : AA** 模式串B: AA*** 匹配串为AAA ,那么匹配到A字符串之后则不能停止,要继续往下搜索。
代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>

using namespace std;

const int MAX_NODE = 2e6+50;
const int CHARSET = 30;
const int maxn = 2e6+50;

int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];
int k = 1;
int triepos[MAX_NODE];

int n, m;
char str[maxn];
int vis[maxn];

inline int idx(char a){
    return a - 'a';
}

int Insert(char w[], int cur){
    int p = 0;
    int len = strlen(w);
    for(int i = 0; i < len; i++){
        int c;
        if(w[i] == '?') c = 26;
        else if(w[i] == '*') c = 27;
        else c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        p = trie[p][c];
    }
    mark[p] = 1;
    return p; // 返回该字符串在trie中的下标
}

void find_ans(char *s, int pos1, int pos2){//pos1为模式串下标, pos2为匹配串下标
    int len = strlen(s);
    int p = pos1;
    if(pos2 == len && mark[pos1]){
        vis[pos1] = 1;
    }
    int c = idx(s[pos2]);
    if(trie[p][c]){
        int tmp = trie[p][c];
        find_ans(s, tmp, pos2+1);
    }
    if(trie[p][26]){//?
        int tmp = trie[p][26];
        find_ans(s, tmp, pos2+1);
    }
    if(trie[p][27]){//*
        int tmp = trie[p][27];
        for(int i = pos2; i <= len; i++){
            find_ans(s, tmp, i);
        }
    }
    return;
}

int main()
{
    scanf("%d %d", &n, &m);
    int i;
    for(i = 0; i < n; i++){
        scanf("%s", str);
        triepos[i] = Insert(str, i);//顺便得到在trie中的下标
    }
    for(i = 0; i < m; i++){
        scanf("%s", str);
        find_ans(str, 0, 0);
        int flag = 0;
        for(int j = 0; j < n; j++){
            if(vis[triepos[j]]){
                flag = 1;
                printf("%d ", j);
            }
        }
        if(!flag)
            printf("Not match");
        puts("");
        memset(vis, 0, sizeof(vis));
    }
    return 0;
}

9.HDU 2846 Repository

链接HDU2864
题意:给你一些P个字符串 和 Q个字符串, 求这Q个字符串分别在这P个字符串中出现过几次, 每个字符串只记一次。
题解:看题目数据发现P最大为1e4, 并且字符串最长为20, 很明显字典树可以写, 处理一下将P个字符出串每一个的后缀包括本身插入字典树中, 并且用一个mark’数组记录一下每一个前缀出现的次数,注意的是可能一个字符串中的不同后缀的一些前缀相同,这里我用一个map记录一下在同一个字符串中的那些前缀出现过,若出翔mark就不加了。
代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 1e6+50;
const int CHARSET = 26;
const int maxn = 1e5+50;

int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE];
int CurNode = 1;

int n, m;
char str[maxn];
map<string, bool> mp;

inline int idx(char a){
    return a - 'a';
}

void Insert(char *w, int pos){
    int p = 0;
    string now = "";
    int len = strlen(w);
    for(int i = pos; i < len; i++){
        now += w[i];
        //cout << pos << " " << now << endl;
        int c = idx(w[i]);
        if(!trie[p][c]){
            trie[p][c] = CurNode++;
        }
        p = trie[p][c];
        if(!mp[now]){
            mark[p]++;
            mp[now] = 1;
            //cout << "#" << endl;
        }
    }
}

int Find_Ans(char *s){
    int p = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++){
        int c = idx(s[i]);
        if(!trie[p][c]){
            return 0;
        }
        p = trie[p][c];
    }
    return mark[p];
}

int main()
{
    scanf("%d", &n);
    while(n--){
        scanf("%s", str);
        mp.clear();
        for(int i = 0; i < strlen(str); i++){
            Insert(str, i);
        }
    }
    scanf("%d", &m);
    while(m--){
        scanf("%s", str);
        cout << Find_Ans(str) << endl;
    }
    return 0;
}

10.CodeForces 817E Choosing The Commander(01字典树入门好题)

题意:第一行一个数 t 表示 接下来的操作数,每一行为一个操作。输入格式为:

  • 1 pi 表示能力值为pi的人加入军营
  • 2 pi 表示能力值为pi的人离开军营
  • 3 pj qj 表示能力值为pj, 领导力为qj 的人来领导军营里的人,输出服从领导的人数,服从领导的条件是当pj 按位异或 pi < qj 时服从领导。

题解:用01字典树,将每一个数用32位二进制表示并插入或删除或比较字典树中的。
代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_NODE = 2e6+50;
const int CHARSET = 5;
const int maxn = 1e5+50;

int trie[MAX_NODE][CHARSET];
int mark[MAX_NODE] = {0};
int k = 1;

int n, op, x, y;

void Insert(int w){
    int p = 0;
    for(int i = 31; i >= 0; i--){
        int c = bool(w&(1 << i));
        //cout << c << endl;
        if(!trie[p][c]){
            trie[p][c] = k++;
        }
        p = trie[p][c];
        mark[p]++;
    }
}

void Delete(int d){
    int p = 0;
    for(int i = 31; i >= 0; i--){
        int c = bool(d&(1 << i));
        p = trie[p][c];
        mark[p]--;
    }
}

int Find_Ans(int pi, int li){
    int p = 0;
    int ans = 0;
    for(int i = 31; i >= 0; i--){
        int c = bool(pi & (1 << i));
        int tl = bool(li & (1 << i));
        if(tl == 0){
            p = trie[p][c];//不管树上节点是什么,c是什么,反正要异或成0,那么直接找c后的节点
        }
        else{
            //cout << "##" << endl;
            //cout << mark[trie[p][c]] << endl;
            ans += mark[trie[p][c]];//ans加上当前异或为0的个数
            p = trie[p][1-c];//找异或为1的节点的后置节点
        }
        if(p == 0) // 没有后置节点了可以直接break
            break;
    }
    return ans;
}

int main()
{
    scanf("%d", &n);
    while(n--){
        scanf("%d", &op);
        if(op == 1){
            scanf("%d", &x);
            Insert(x);
        }
        else if(op == 2){
            scanf("%d", &x);
            Delete(x);
        }
        else if(op == 3){
            scanf("%d %d", &x, &y);
            int ans = Find_Ans(x, y);
            printf("%d\n", ans);
        }
    }
    return 0;
}

发布了23 篇原创文章 · 获赞 6 · 访问量 823

猜你喜欢

转载自blog.csdn.net/wxy2635618879/article/details/104192857