第二部分 字符串算法 (第四章 Trie) 例题

本文介绍了Trie树在字符串统计、最大异或对、最长亦或路径以及阅读理解等信息处理问题中的应用。通过Trie树实现高效的数据结构,能够快速查询和计算字符串的相关信息,如结束节点计数、最大异或值和最长异或路径。此外,还讨论了如何使用Trie树优化空间复杂度以避免内存限制。
摘要由CSDN通过智能技术生成

例题一:前缀统计 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)的异或路径的长度是多少。

那我们考虑能不能用这个表示出任意两个点之间的异或路径。
这里先给出结论,其实就是两个点到根节点的异或路径异或起来得出的值。

我们来证明:
分两种情况,分别是一个点在另一个点到根节点的路径上,要么就是两条路径是分开的,不会相交。

  1. 第一种,那我们可以知道一个点,就是一个值异或它自己就是 0 00,就会消掉。那你想想,第一种情况时这个图:在这里插入图片描述
    那 1 11 号点到根节点的异或路径就是 a,2号点到根节点的异或路径是 a ⊕ b a\oplus b ab,我们要的是 b。
    那你发现,把它们异或起来,就是 a⊕a⊕b=b。(两个 a 异或起来抵消掉了)
  2. 第二种,那我们可以画图。
    在这里插入图片描述
    那 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值