Trie树集训
[P3879 TJOI2010]阅读理解 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
字典树裸题
给你一个文章,让你求出每个单词所属的句子。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
using namespace std;
const int N = 1e6 + 50, M = 5e5 + 50;
int son[N][26], n, m, cnt, q;
char s[1010];
bitset<1101>b[M];
void insert(char *s, int pos)
{
int p = 0;
for(int i = 0; s[i] != '\0'; i++)
{
int x = s[i] - 'a';
if(!son[p][x]) son[p][x] = ++cnt;
p = son[p][x];
}
b[p][pos] = 1;
}
void query(char *s)
{
int p = 0;
for(int i = 0; s[i] != '\0'; i++)
{
int x = s[i] - 'a';
if(!son[p][x])
{
printf("\n");
return;
}
p = son[p][x];
}
for(int i = 1; i <= n; i++)
{
if(b[p][i] == 1) printf("%d ", i);
}
printf("\n");
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &m);
for(int j = 1; j <= m; j++)
{
scanf("%s",s);
insert(s, i);
}
}
scanf("%d", &q);
while(q--)
{
scanf("%s", s);
query(s);
}
return 0;
}
[P2292 HNOI2004]L语言 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Trie加上记忆化搜索
枚举长字符串的时候,注意把每一次询问的结果进行存储。
这样可以保证时间复杂度满足要求。
就是隔一段打个标记,代表最远可以匹配到哪里,但是要注意一点:每次匹配单词都要从字典树的根节点开始匹配,如果匹配到了这个单词的末尾,就在当前位置打个标记,也就是说我们需要两重循环枚举,第一重循环枚举当前记录答案的位置,第二重循环枚举最远可以到达什么地方。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 2e6 + 60;
char s[N];
int son[N][26], id;
bool flag[N], vis[N];
int n, m, ans;
map<string, bool>m1;
map<string, int>m2;
void insert(char *s)
{
int p = 0;
for(int i = 0; s[i] != '\0'; i++)
{
int x = s[i] - 'a';
if(!son[p][x]) son[p][x] = ++id;
p = son[p][x];
}
flag[p] = 1;
}
int query(char *s)
{
if(m1[s]) return m2[s];
int len = strlen(s + 1), p;
memset(vis, 0, sizeof(vis));
vis[0] = 1;
for(int i = 0; i <= len; i++)
{
if(vis[i]) ans = i;
else continue;
p = 0;
for(int j = i + 1; j <= len; j++)
{
int x = s[j] - 'a';
if(!son[p][x])
{
break;
}
p = son[p][x];
if(flag[p]) vis[j] = 1;
}
}
m1[s] = true;
m2[s] = ans;
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%s", s);
insert(s);
}
for(int i = 1; i <= m; i++)
{
scanf("%s", s + 1);
int ans = query(s);
printf("%d\n", ans);
}
return 0;
}
[P2922 USACO08DEC]Secret Message G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
如果Trie无法走了,答案为res
否则为以当前的点为终点或以此点为前缀的点。
每一次查询的结果为 r e s − a n s [ p ] + s u m [ p ] res - ans[p] + sum[p] res−ans[p]+sum[p]
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e5 + 50, M = 1e4 + 50;
int n, m, id, cnt = 1;
char s[M];
int son[N][2], ans[N], sum[N];
void insert(char *s)
{
int p = 1;
for(int i = 0; i < id; i++)
{
int x = int(s[i] - '0');
if(son[p][x] == -1) son[p][x] = ++cnt;
p = son[p][x];
sum[p] ++;
}
ans[p]++;
}
int query(char *s)
{
int p = 1, res = 0;
for(int i = 0; i < id; i++)
{
int x = int(s[i] - '0');
if(son[p][x] == -1) return res;
p = son[p][x];
res += ans[p];
}
return res - ans[p] + sum[p];
}
int main()
{
memset(son, -1, sizeof(son));
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
memset(s, '\0', sizeof(s));
int x;
scanf("%d", &x);
id = 0;
for(int i = 1; i <= x; i++)
{
int u;
scanf("%d", &u);
s[id++] = char(u + '0');
}
insert(s);
}
for(int i = 1; i <= m; i++)
{
memset(s, '\0', sizeof(s));
int x;
scanf("%d", &x);
id = 0;
for(int i = 1; i <= x; i++)
{
int u;
scanf("%d", &u);
s[id++] = char(u + '0');
}
int ans = query(s);
printf("%d\n", ans);
}
return 0;
}
[P3065 USACO12DEC]First! G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) (字典树上拓扑排序)
题意:重新排列字母表的顺序来计算输入中有哪些字符串可以排在第一个(即字典序最小)
看到与字典序有关的问题,很容易想到建一棵 Trie(字典树) 。
对于每一个字符串,我们可以设它的字典序是所有字符串中最小的。
也就是说,这个字符串的第 i 个字母 在 Trie 的第 i 层(根节点算第 0 层)的所有字母中 字典序最小。
设这个字符串的第 i 个字母为 u,我们可以连单向边 u → v u \to v u→v,表示我们指定了 u 的字典序比 v小,其中 v 是第 i 层的其它字母。若这个字符串是其它某个字符串的前缀,则这个字符串不可能成为字典序最小的串,比如说 abba 的字典序一定比 ab 大。当 26 个字母间的关系形成环时,也一定不能成为字典序最小的串。
怎么判断是否形成环呢?可以用 t a r j a n \rm tarjan tarjan 或者 拓扑排序 。
这里我采用了 拓扑排序 。我们从入度为 0 的点开始,不断删去与它相连的边,并修改其它点的入度,将新的入度为 0 的点加入队列。若队列已空,但还存在入度不为 0 的点,则说明图存在环,反之则有解。
时间复杂度为 O ( 26 m ) O(26m) O(26m)。