例题一:前缀统计 link
考虑将N个字符串存入Trie树,再记录一个值end表示字符串的结尾是Trie上节点p的字符串个数。
最后查询函数时,将其加总即可。
#include<cstdio>
#include<cstring>
using namespace std;
struct Trie {
char x;
int son[301], end;
} trie[1000001];
int n, m, cn, now, KK, re;
char c[1000001];
void build() {
now = 0;
for (int i = 0; i < cn; i++) {
if (!trie[now].son[c[i]]) {
trie[now].son[c[i]] = ++KK;
trie[now].x = c[i];
}
now = trie[now].son[c[i]];
}
trie[now].end++;
}
int find() {
now = 0;
re = 0;
for (int i = 0; i < cn; i++) {
if (!trie[now].son[c[i]]) return re;
now = trie[now].son[c[i]];
re += trie[now].end;
}
return re;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", c);
cn = strlen(c);
build();
}
for (int i = 1; i <= m; i++) {
scanf("%s", c);
cn = strlen(c);
printf("%d\n", find());
}
return 0;
}
例题二:最大异或对link
我们考虑异或是干嘛的——就是在二进制中相同为 0,不同为 1。
那我们要让异或值最大,就是要选出来的数最高位尽可能不同。
那我们可以用 Trie 树,让一个点有两个儿子,分别代表下一位是 0还是 1。
但这时候有个问题,我们是要根节点是最高位还是根节点是最低位呢?
我们想象一下,离根节点越近,它匹配的优先级就越高,那肯定就是越高位,就离根节点越近。
前面的位没有就补 0。
那我们建树就弄好了,接着看看怎么查询某个数与前面的数匹配。
那首先从根节点下来就是从高位到低位,对于这一位,有不同的就选不同的,不然就看有没有相同的,如果都没有,那后面都是 0,就可以直接退了。
这个其实就是贪心。
当然如果有不同的才有对答案的贡献。
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1e7 + 10;
int n, a, tot, trie[N][3], ans;
void insert(int x)
{
int p = 0;
for(int i = 31; i >= 0; i--)
{
int str = (x >> i) & 1;
if(!trie[p][str])
trie[p][str] = ++tot;
p = trie[p][str];
}
}
void query(int x)
{
int p = 0, sum = 0;
for(int i = 31; i >= 0; i--)
{
int str = !((x >> i) & 1);
if(trie[p][str]) sum += (1 << i);
else str = !str;
p = trie[p][str];
}
ans = max(ans, sum);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a);
query(a), insert(a);
}
printf("%d\n", ans);
return 0;
}
例题三:最长亦或路径 link
首先看到要异或的值最大,我们要想到可以用 Trie 树来贪心弄。
但是它好像不知道怎么弄,那我们先不管它。
那我们看到是一棵树,那我们可以试着统计 i ii 到根节点(我这里设是 1 11)的异或路径的长度是多少。
那我们考虑能不能用这个表示出任意两个点之间的异或路径。
这里先给出结论,其实就是两个点到根节点的异或路径异或起来得出的值。
我们来证明:
分两种情况,分别是一个点在另一个点到根节点的路径上,要么就是两条路径是分开的,不会相交。
- 第一种,那我们可以知道一个点,就是一个值异或它自己就是 0 00,就会消掉。那你想想,第一种情况时这个图:
那 1 11 号点到根节点的异或路径就是 a,2号点到根节点的异或路径是 a ⊕ b a\oplus b a⊕b,我们要的是 b。
那你发现,把它们异或起来,就是 a⊕a⊕b=b。(两个 a 异或起来抵消掉了) - 第二种,那我们可以画图。
那 1号点到根节点的异或路径就是 a,2 号点到根节点的异或路径是 b,我们要的是 a⊕b。
那你发现,把它们异或起来,就是 a⊕b。
那你就可以一开始预处理出到根节点的异或路径,然后枚举两个点,然后算这两个点的异或路径,然后取最大值。
但是很明显这样是
O
(
n
2
)
O(n^2)
O(n2) 的,它会超时。
那我们就想一想有什么方法可以快速求最大值的。
想想我们之前一开始想用什么方法?
没错,就是 Trie 树。
我们可以把每个点到根节点的异或路径都放进 Trie 树里面构造。
然后每次枚举你要的异或路径的另一个点,然后跟 Trie 树里面的路径匹配找到最大值。
前面做过一题就是求这个最大值的,主要的就是用了贪心的思想。
从高位向低位枚举,然后如果有跟你这一位不同的就优先选,同时统计这一位异或之后是 1对数的贡献。然后如果没有不同的,就看有没有相同的。
(因为毕竟你可以这一位相同,然后尽可能让后面更高的位不同,这样的贡献就更大)
那如果想相同不相同都没有,那就只能以当前的贡献退出了。
最后你会发现,它就是上一题。
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
struct node { int u, v, w, next; } edge[N * 2];
int n, x, y, z, tot, ans, dis[N], head[N], trie[N][3];
bool vis[N];
inline void add(int x, int y, int z)
{
edge[++tot] = (node){x, y, z, head[x]};
head[x] = tot;
}
inline void dfs(int now)
{
for(int i = head[now]; i; i = edge[i].next)
{
int next = edge[i].v;
if(!vis[next])
{
dis[next] = dis[now] ^ edge[i].w;
vis[next] = 1;
dfs(next);
}
}
}
inline void insert(int x)
{
int p = 0;
for(int i = 31; i >= 0; i--)
{
int str = (x >> i) & 1;
if(!trie[p][str]) trie[p][str] = ++tot;
p = trie[p][str];
}
}
void query(int x)
{
int p = 0, sum = 0;
for(int i = 31; i>= 0; i--)
{
int str = !((x >> i) & 1);
if(trie[p][str]) sum += 1 << i;
else str = !str;
p = trie[p][str];
}
ans = max(ans, sum);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
tot = 0;
vis[0] = 1;
dfs(1);
for(int i = 1; i <= n; i++) query(dis[i]), insert(dis[i]);
printf("%d\n", ans);
return 0;
}
例题四:阅读理解 link
其实这道题我们考虑将短文中的所有单词都存入Trie,
考虑在Trie上的接点上赋一个域flag[p][i],表示以p结尾的字符串是否在第i篇短文中。
在最后查询时加总即可,类似例题一。
但细心的你一定会发现,这样会MLE,于是考虑bitset函数。
这个我也不太清楚,总之它每次所占空间只有一bit,可以上网搜搜。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <bitset>
using namespace std;
const int N = 500007;
int n, m, l, len, tot, cnt, trie[N][30];
bitset<1001> flag[N];
bool fl;
char s[30];
inline void insert(int x)
{
len = strlen(s);
int p = 0;
for(int i = 0; i < len; i++)
{
int str = s[i] - 'a';
if(!trie[p][str]) trie[p][str] = ++tot;
p = trie[p][str];
}
flag[p][x] = 1;
}
inline void query()
{
len = strlen(s);
int p = 0;
fl = 0;
for(int i = 0; i < len; i++)
{
int str = s[i] - 'a';
if(!trie[p][str])
{
fl = 1;
break;
}
p = trie[p][str];
}
if(!fl)
{
for(int i = 1; i <= n; i++)
{
if(flag[p][i])
printf("%d ", i);
}
}
printf("\n");
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &l);
for(int j = 1; j <= l; j++)
{
scanf("%s", s);
insert(i);
}
}
scanf("%d", &m);
for(int i = 1; i<= m; i++)
{
scanf("%s", s), query();
}
return 0;
}