字典树的静态建树



前言

数学大战即将来袭!所以写的少一些,还没怎么看数学呢!
字典树又称前缀树,顾名思义,用公共前缀来存储字符串,优点是效率高,但是缺点也明显,就是每个节点都要有一个next指针数组,极大的浪费内存
字典树可以动态建树,即每个节点都是new出来的,也可以静态建树,即用一个tot变量为每个节点连上一个node数组中的元素,因为ACM追求时间效率,所以本文通篇采用静态建树

  1. 动态建树与静态建树
    https://www.cnblogs.com/George1994/p/6346790.html
  2. Trie树详解
    https://segmentfault.com/a/1190000008877595

不会做的题

  1. POJ 2513 - Colored Sticks
  2. HDU 2846 - Repository

统计难题 - HDU 1251

题目大意
OMIT!

思路
这道题太邪门了……选G++过不了题,会Memory Limit Exceeded……选C++才过了
还有,不给范围,天!诛!地!灭!
这道题就是所走过之处,cnt++,然后查询的时候输出cnt就完事了
不过这题get了个点是gets的话,如果为空行,那么str[0]会被它改为NULL

  #include <cstdio>
  #include <cstring>
  using namespace std;
  const int N = 5e5 + 15;

  int  cnt[N];        //计数
  int  nxt[N][26];    //next指针
  char str[12];
  int  tot;           //节点分配

  inline void init(){
      tot = 1;        //从1开始,因为0给了root节点
      memset(cnt, 0, sizeof(cnt));
      memset(nxt[0], -1, sizeof(nxt));
  }

  void push(char* s){
      int p = 0;
      for( ; * s; s++){
          int idx = * s - 'a';
          if(nxt[p][idx] == -1){      //如果下一个节点不存在
              nxt[p][idx] = tot++;    //分配节点
          }
          cnt[nxt[p][idx]]++;         //走过之处cnt++
          p = nxt[p][idx];            //走向下一个节点
      }
  }

  int query(char* s){
      int p = 0;
      for( ; *s; s++){
          int idx = * s - 'a';
          if(nxt[p][idx] == -1)   return 0;   //没走完直接返回0
          p = nxt[p][idx];
      }
      return cnt[p];
  }

  int main(){
      init();
      while(gets(str)){
          if(!(*str))    break;
          push(str);
      }
      while(gets(str)){
          printf("%d\n", query(str));
      }
  }


Phone List - HDU 1671

题目大意
给定电话号码,求是否任意一个电话号码都不是其他电话号码的前缀

思路
模板题
一边加入单词一边建树,那么“任意一个电话号码都不是其他电话号码的前缀”就体现在:
这个单词不是别的单词的前缀: 加入过程中有new一个新结点,说明一定不是别的单词的前缀
别的单词不是这个单词的前缀: 加入过程中没经过end == true的节点,说明别的单词一定不是它的前缀

  #include <cstdio>
  #include <iostream>
  #include <algorithm>
  #include <cstring>
  using namespace std;
  const int N = 1e5 + 15;

  bool mend[N];
  int  nxt[N][10];
  int tot;          //控制节点的分配

  inline void init(){
      tot = 1;
      memset(mend, false, sizeof(mend));
      memset(nxt[0], -1, sizeof(nxt));
  }

  inline bool push(char str[]){
      bool ans = false;
      bool after = true;
      int p = 0;
      for( ; * str; str++){
          int idx = * str - '0';
          if(nxt[p][idx] == -1){   //如果没有这个节点就创建
              nxt[p][idx] = tot++;
              after = false;    //后面肯定无节点
          }
          ans |= (mend[nxt[p][idx]]);  //判断是否经过某个单词的end
          p = nxt[p][idx];
      }
      mend[p] = true;
      return (ans == false && after == false);
  }

  int main(){
      char str[12];
      int t;
      scanf("%d", &t);
      while(t--){
          bool flag = true;
          init();
          int n;
          scanf("%d", &n);
          while(n--){
              scanf("%s", str);
              if(flag && !push(str))   flag = false;
          }
          printf("%s\n", flag ? "YES" : "NO");
      }
  }


Xor Sum - HDU 4825

题目大意
Omit as well

思路
这题目一开始看上去和Trie树并没有什么关系……
但是不妨想一想,要是异或值最大,就要 每一位都和当前的查询的数的对应位相反
所以可以先把所给集合的每一个数以二进制形式存入树中,查询的数取反后查询,如果有这一位,那么就往下执行,如果没有,那就只能先妥协,走另一边,而另一边是一定有节点的,因为我们的数字都是全部位存进去的,而位只能是0和1,所以集合只要非空,一定有一条边可以走到底,不管它是不是妥协的

  #include <cstdio>
  #include <cstring>
  using namespace std;
  const int N = 4e6 + 15;

  struct pNode{
      unsigned val;
      pNode* next[2];
  };

  pNode  node[N];
  pNode* root;
  int tot;

  inline void nodeInit(pNode*& nd){
      nd->val = -1;
      memset(nd->next, 0, sizeof(nd->next));
  }
  inline void init(){
      tot = 0;
      root = &node[tot++];
      nodeInit(root);
  }

  void push(unsigned val){
      unsigned b = val;
      pNode* p = root;
      for(int t = 0; t < 32; t++, b <<= 1){
          unsigned idx = ((b >> 31)&1);
          pNode*& pnext = p->next[idx];
          if(!pnext){
              pnext = &node[tot++];
              nodeInit(pnext);
          }
          p->val = val;
          p = pnext;
      }
      p->val = val;
  }

  int query(unsigned val){
      unsigned b = ~val;
      pNode* p = root;
      for(int t = 0; t < 32; t++, b <<= 1){
          unsigned idx = ((b >> 31)&1);
          if(p->next[idx])   p = p->next[idx];
          else               p = p->next[idx^1];
      }
      return p->val;
  }

  int main(){
      int t;
      int caseno = 1;
      scanf("%d", &t);
      while(t--){
          init();
          int n, m;
          scanf("%d%d", &n, &m);
          while(n--){
              unsigned v;
              scanf("%u", &v);
              push(v);
          }
          printf("Case #%d:\n", caseno++);
          while(m--){
              unsigned q;
              scanf("%u", &q);
              printf("%u\n", query(q));
          }
      }

      return 0;
  }


T9 - POJ 1451

题目大意
题目太长,很想omit
本题其实就是输入法的9键模式
注意本题中相同前缀的机率是相加的(因为这个WA了 T^T)
就这样,基本Omit!

思路
开个数组保存字典,然后建两棵树
第一棵树插入字符串,所走之处把对应字符串的机率加上去,并且写入对应字符串在字典中的位置,这样就得到了前缀机率相加后的字典树
第二棵树插入字符串对应数字,所走之处不断更新当前机率最大对应的字符串在字典中的位置,并更新节点的最大机率
最后问啥查啥,注意走到str[i] == ‘1’就可以了
(这题真的是模板题嘛?)

  #include <cstdio>
  #include <cstring>
  using namespace std;
  const int N = 1000 + 15;
  const int M = 100 + 15;

  struct pNode{
      int p, lv;
      pNode* next[26];
  };
  const int trans[] = {2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,9};
  char  str[N][M];
  int   lv[N];
  pNode node[N], word[N];   //node是字符串对应数字的节点数组,word是字符串节点数组
  pNode *root, *wroot;
  int   tot, wtot;

  inline void nodeInit(pNode*& nd){
      memset(nd->next, 0, sizeof(nd->next));
      nd->p = -1, nd->lv = 0;
  }
  inline void init(){
      tot = 0, wroot = 0;
      root = &node[tot++];
      wroot = &word[wtot++];
      nodeInit(root);
      nodeInit(wroot);
  }

  void build_word(int n){
      for(int i = 0; i < n; i++){
          char* s = str[i];
          pNode* p = wroot;
          while(*s){
              int idx = *s - 'a';
              pNode*& pnext = p->next[idx];
              if(!pnext){
                  pnext = &word[wtot++];
                  nodeInit(pnext);
              }
              pnext->p = i;
              pnext->lv += lv[i];
              p = pnext;
              s++;
          }
      }
  }

  void build(int n){
      build_word(n);
      for(int i = 0; i < n; i++){
          char* s = str[i];
          pNode* p = root;
          pNode* wp = wroot;
          while(*s){
              int idx = trans[* s - 'a'];
              pNode*& pnext = p->next[idx];
              pNode*& wpnext = wp->next[* s - 'a'];
              if(!pnext){
                  pnext = &node[tot++];
                  nodeInit(pnext);
              }
              if((pnext->lv) < (wpnext->lv)){
                  pnext->lv = wpnext->lv;
                  pnext->p = wpnext->p;
              }
              p = pnext;
              wp = wpnext;
              s++;
          }
      }
  }
  void query(char s[]){
      int i;
      pNode* p = root;
      for(i = 0; s[i] != '1'; i++){
          int idx = s[i] - '0';
          pNode*& pnext = p->next[idx];

          if(!pnext)  break;
          for(int j = 0; j <= i; j++)     putchar(str[pnext->p][j]);
          puts("");
          p = pnext;
      }
      while(s[i] != '1'){
          puts("MANUALLY");
          i++;
      }
  }

  int main(){
      char q[M];
      int t;
      int caseno = 1;
      scanf("%d", &t);
      while(t--){
          init();
          int n, m;
          scanf("%d", &n);
          for(int i = 0; i < n; i++){
              scanf("%s%d", str[i], &lv[i]);
          }
          build(n);
          scanf("%d", &m);
          printf("Scenario #%d:\n", caseno++);
          while(m--){
              scanf("%s", q);
              query(q);
              puts("");
          }
          puts("");
      }
  }