题目来自《算法竞赛设计指南》

Tire树是一种可以快速查找字符串的数据结构

模板

#include<cstdio>
#include<algorithm>
#include<cstring>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1123;
int tire[MAXN][26], tot = 1;
bool end[MAXN]; void add(char* str)
{
int len = strlen(str), p = 1;
REP(i, 0, len)
{
int ch = str[i] - 'a'; //这里是a不是0
if(!tire[p][ch]) tire[p][ch] = ++tot;
p = tire[p][ch];
}
end[p] = true;
} bool search(char* str)
{
int len = strlen(str), p = 1;
REP(i, 0, len)
if(!(p = tire[p][str[i] - 'a']))
return false;
return end[p];
} int main()
{
int n, m;
scanf("%d%d", &n, &m);
REP(i, 0, n)
{
char s[15];
scanf("%s", s);
add(s);
}
while(m--)
{
char s[15];
scanf("%s", s);
printf("%s\n", search(s) ? "Yes": "No");
}
return 0;
}

例题.前缀统计

问题: 给n个字符串和m次询问,每次询问给定一个串T,输出有多少个字符串是T的前缀

解答:加入每个字符串的时候在结尾节点加1, 给T后在Tire树上搜一遍,加上沿途字符串结尾的值即可。注意可能有重复的字符串。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1123;
int tire[MAXN][26], tot = 1;
int end[MAXN]; //避免重复字符串 void add(char* str)
{
int p = 1;
REP(i, 0, strlen(str))
{
int ch = str[i] - 'a';
if(!tire[p][ch]) tire[p][ch] = ++tot;
p = tire[p][ch];
}
end[p]++;
} int search(char* str)
{
int p = 1, res = 0;
REP(i, 0, strlen(str))
{
if(!(p = tire[p][str[i] - 'a'])) break;
res += end[p];
}
return res;
} int main()
{
int n, m;
scanf("%d%d", &n, &m);
REP(i, 0, n)
{
char s[15];
scanf("%s", s);
add(s);
}
while(m--)
{
char s[15];
scanf("%s", s);
printf("%d\n", search(s));
}
return 0;
}

hdu 1251

和上一题类似,只不过反过来而已

问题: 给n个字符串和m次询问,每次询问给定一个串T,输出T是多少字符串的前缀

解答:可以dfs一遍处理出来每个节点的子树中(包括节点本身)下有多少个字符终点

然后搜到T的最后一个字符,输出预处理的结果即可

注意这道题的输入比较麻烦,判断*s是否为空

#include<cstdio>
#include<algorithm>
#include<cstring>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e6;
int tire[MAXN][26], d[MAXN], tot = 1;
bool End[MAXN]; void add(char* str)
{
int p = 1;
REP(i, 0, strlen(str))
{
int ch = str[i] - 'a';
if(!tire[p][ch]) tire[p][ch] = ++tot;
p = tire[p][ch];
}
End[p] = true;
} int dfs(int p)
{
if(End[p]) d[p]++;
REP(ch, 0, 26)
{
if(tire[p][ch])
d[p] += dfs(tire[p][ch]);
}
return d[p];
} int search(char* str)
{
int p = 1, res = 0;
REP(i, 0, strlen(str))
if(!(p = tire[p][str[i] - 'a'])) return 0;
return d[p];
} int main()
{
char s[15];
while(gets(s) && *s) add(s);
dfs(1);
while(~scanf("%s", s)) printf("%d\n", search(s));
return 0;
}

【例题】 The XOR Largest Pair

问题:给n个整数A1, A2……An,选出两个数进行异或,得到的结果最大是多少?N <= 1e5, 0 <= Ai < 2^31

解答:我一开始的是把每个数字用二进制的形式表示,然后可以建一颗字典树。然后公共部分肯定异或的值为1.

然后就找最深的结点的数作为第一个数……然后发现找另外一个数就很麻烦了。

看了题解发现要倒着存,也就是从第31位,到第30位……然后每次加入一个数遍历字典树,每次尽量找与当前位不同的数字,这样可以保证最终的结果最大。顺着存就很麻烦。

最后把每个数算出的结果取max即可

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 4e6;
int tire[MAXN][2], tot = 1; void read(int& x)
{
int f = 1; x = 0; char ch = getchar();
while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }
x *= f;
} void add(int x)
{
int p = 1;
for(int i = 30; i >= 0; i--)
{
int now = (x >> i) & 1;
if(!tire[p][now]) tire[p][now] = ++tot;
p = tire[p][now];
}
} int search(int x)
{
int p = 1, res = 0;
for(int i = 30; i >= 0; i--)
{
int now = (x >> i) & 1;
if(tire[p][now ^ 1])
{
p = tire[p][now ^ 1];
res |= (1 << i);
}
else p = tire[p][now]; //
}
return res;
} int main()
{
int n; read(n);
int ans = 0;
REP(i, 0, n)
{
int x; read(x);
add(x);
ans = max(ans, search(x));
}
printf("%d\n", ans);
return 0;
}

poj 3764

一开始感觉求路径很麻烦,就不知道怎么做

看了题解发现,原来可以用lca的思想来做,lca之前的部分异或起来都是0.所以x到根的值 ^ y到根的值 = x到y的值

那么我们可以预处理每个节点到根的值,那么就转化成上一道题了。

然后这道题丧心病狂的卡常!!我用vector存边,然后就被卡了……第一次用vector被卡……

所以我还是无奈去学了邻接表,过了,0.5S。然后闲着无聊看看读优的效果,去掉读优后直接飙到0.94S

读优大发好!

然后注意一些细节就好了。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e5 + 10;
const int MAXM = 4e6;
int tire[MAXM][2], n, tot, num;
int d[MAXN], head[MAXN];
struct edge { int to, w, next; };
edge e[MAXN << 1]; //双向边乘以2 void addedge(int from, int to, int w)
{
e[tot] = edge{to, w, head[from]};
head[from] = tot++;
} void read(int& x)
{
int f = 1; x = 0; char ch = getchar();
while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }
x *= f;
} void dfs(int u, int fa)
{
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to, w = e[i].w;
if(v == fa) continue;
d[v] = d[u] ^ w;
dfs(v, u);
}
} void add(int x)
{
int p = 1;
for(int i = 30; i >= 0; i--)
{
int now = (x >> i) & 1;
if(!tire[p][now]) tire[p][now] = ++num;
p = tire[p][now];
}
} int search(int x)
{
int p = 1, res = 0;
for(int i = 30; i >= 0; i--) //0到2^31-1即从第30位到第0位
{
int now = (x >> i) & 1;
if(tire[p][now ^ 1])
{
p = tire[p][now ^ 1];
res |= (1 << i);
}
else p = tire[p][now];
}
return res;
} int main()
{
while(~scanf("%d", &n))
{
memset(tire, 0, sizeof(tire));
memset(head, -1, sizeof(head));
num = 1; tot = 0; //注意这里要清零,不只是是数组要清 REP(i, 1, n)
{
int u, v, w;
read(u); read(v); read(w);
addedge(u, v, w);
addedge(v, u, w);
} dfs(0, -1); int ans = 0;
REP(i, 0, n)
{
ans = max(ans, search(d[i]));
add(d[i]);
}
printf("%d\n", ans);
} return 0;
}

poj 3630

以这个很裸的一道题作为结尾

输入一堆字符串,判断有没有字符串是另外一个的前缀。

有两种情况是

(1)加入的时候没有创造新的节点,那么这个字符串就是其他字符串的前缀

(2)如果加入的时候遇到了字符终点,那么就有字符串是当前串的前缀

#include<cstdio>
#include<cctype>
#include<cstring>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e4 + 10;
int tire[MAXN * 10][15], end[MAXN * 10], tot = 1; bool add(char* s)
{
int p = 1;
bool ok = false;
REP(i, 0, strlen(s))
{
if(end[p]) return false;
int ch = s[i] - '0';
if(!tire[p][ch]) tire[p][ch] = ++tot, ok = true;
p = tire[p][ch];
}
end[p] = 1;
return ok;
} int main()
{
int T, n;
scanf("%d", &T); while(T--)
{
scanf("%d", &n);
bool ans = true;
tot = 1;
memset(tire, 0, sizeof(tire));
memset(end, 0, sizeof(end)); REP(i, 0, n)
{
char s[15];
scanf("%s", s);
if(!add(s)) ans = false;
}
printf("%s\n", ans ? "YES" : "NO");
} return 0;
}

Tire树总结(模板+例题)的更多相关文章

  1. 【数据结构与算法】Trie(前缀树)模板和例题

    Trie 树的模板 Trie 树的简介 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树.他的核心思想是空间换 ...

  2. Tire树模板-于是他错误的点名开始了

    题目背景 XS中学化学竞赛组教练是一个酷爱炉石的人. 他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛CON900). ...

  3. KMP+Tire树(模板)

    \(\color{Red}{KMP板子}\) #include <bits/stdc++.h> using namespace std; const int maxn=1e6+9; int ...

  4. Tire树(字典树)

    from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,P ...

  5. Tire树简介

    又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种. 典型应用:用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计. 它的优点是:利用字符串的公共 ...

  6. Codeforces 714C. Sonya and Queries Tire树

    C. Sonya and Queries time limit per test:1 second memory limit per test: 256 megabytes input:standar ...

  7. 中文分词系列(二) 基于双数组Tire树的AC自动机

    秉着能偷懒就偷懒的精神,关于AC自动机本来不想看的,但是HanLp的源码中用户自定义词典的识别是用的AC自动机实现的.唉-没办法,还是看看吧 AC自动机理论 Aho Corasick自动机,简称AC自 ...

  8. 中文分词系列(一) 双数组Tire树(DART)详解

    1 双数组Tire树简介 双数组Tire树是Tire树的升级版,Tire取自英文Retrieval中的一部分,即检索树,又称作字典树或者键树.下面简单介绍一下Tire树. 1.1 Tire树 Trie ...

  9. [数据结构]字典树(Tire树)

    概述: Trie是个简单但实用的数据结构,是一种树形结构,是一种哈希树的变种,相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串.和普通树不同的地方是,相同的字符 ...

随机推荐

  1. Statement对象sql注入漏洞的问题

    现在通过mysql以及oracle来测试sql注入  漏洞 mysql中的注释#    oracle中的注释为-- 所以注入漏洞就产生了 //登录测试 public void login()throw ...

  2. Java并发之线程间的同步协作与通信协作

    1,Monitor监视器与syncrhoized实现原理 1.1:Monitor Monitor是一个同步工具,相当于操作系统中的互斥量(mutex),即值为1的信号量. 它内置与每一个Object对 ...

  3. 机房工程-在线式、后备式UPS选择(转载)

    原文网址:http://oa.yesky.com/10/31061510all.shtml#p31061510 1后备式UPS还是在线式UPS? 作为机房设备的一项重要保护措施,UPS起着无可替代的作 ...

  4. Android漫游记(6)---APP启动之旅(I)

    Android基于Linux2.6+内核,我们看一张图,以对Android系统的架构有个感性的认识. 我们从Kernel层简单说明: 1.Kernel层:基于Linux2.6+内核.同一时候做了一些嵌 ...

  5. ST Nucleo mbed套件开发 一 MBED环境使用 以Nucleo-F401为例 (二)

    MBED环境.使用起来总是那么的别扭可能很多人不习惯用在线编程器,大多数做ST32开发的都比較喜欢KEIL或者IAR,有没有什么好的方法呢.我们能够本地编译MBEDproject, 答案是肯定了.下来 ...

  6. rails 安装后调整gem sources 地址

    rails 安装后调整gem sources 地址 使用https会有认证的问题: 移除原有的: gem sources --remove https://rubygems.org/ 查看当前的: g ...

  7. bram和dram差别

    选择distributed memory generator和block memorygenerator标准: Dram和bram差别: 1.bram 的输出须要时钟,dram在给出地址后既可输出数据 ...

  8. Apache Pig的前世今生

    近期,散仙用了几周的Pig来处理分析我们站点搜索的日志数据,感觉用起来非常不错,今天就写篇笔记介绍下Pig的由来,除了搞大数据的人,可能非常少有人知道Pig是干啥的.包含一些是搞编程的,但不是搞大数据 ...

  9. python类的继承和多态,获取对象信息

    继承 类的继承机制使得子类可以继承父类中定义的方法,拥有父类的财产,比如有一个Animal的类作为父类,它有一个eat方法: class Animal(object): def __init__(se ...

  10. IPython Autoreload

    在PyCharm中进行代码调试的时候, 设置修改的模块自动重新载入是非常方便的 In [1]: %load_ext autoreload In [2]: %autoreload 2