字典树

零、前言

这是一篇高开低走的博客

一、普通Trie树

1.定义?

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

摘自百度百科

2.讲解

又到了喜闻乐见的百度百科自学时间

如果你一头雾水地回来了,那我就成功了,嘿嘿嘿

(1).小知识

它可以用来干嘛?

做题!这不废话

定义?

而且它的时间复杂度为(O(m|S|))(一般是这样)

(|S|)为字符串长度,(n)个字符串,(m)为操作次数,但其实建树也需要时间(O(n|S|))

字典树开的空间当然就是有多少点就开多少空间(记得加上可能加的点的空间)

(2).具体实现

我们先讲建树

假设我们有(26)个小写字母,组成了长度不超过(50)的单词 (谁家单词这么长) 字符串

比如有:

(aba,aaa,aa,bc)

我们就可以这么建树:

首先我们需要一个超级源点 (原点?),我比较喜欢用(0)号点(因为不用赋值就是(0))

红色数字为点的编号,蓝色圈圈中的黑色字母为当前点的字母,绿色数字为(cnt),用来记录字符串在哪里结束,有多少个

然后插入第一个字符串(aba)

我们具体的操作为:从源点出发,看一看有没有第一个字母(a)曾经出现过

如果没有,新建一个点作为当前点(源点)的(a)号儿子,然后走到这个儿子上

如果有,直接走到这个儿子上

当我们走到最后一步时,我们需要一个标记表示走到这步的时候就是一个字符串了

所以在每个点中,我们需要一个(cnt)记录字符串出现次数

在我们的这道例题中,当然就有(26)个儿子,一个(cnt)

经过第一个插入操作,我们可以得到这么一棵字典树:

然后我们插入第二个单词:

第三个单词:

由于之前已经有(aa)了,所以我们只需走到(aa)( ext{++}cnt)就好了

第四个单词,另起一个枝干:

如果你想查询是否存在一个字符串(S),只需要从源点开始,一步一步走,如果到不了终点或者终点的(cnt)(0),就是没有这个字符串

反之则有

最大败笔:图太丑!!!

3.练习

于是他错误的点名开始了(洛谷)

阅读理解(洛谷)

最长异或路径(洛谷)

4.代码

阅读理解代码

//12252024832524
#include <set>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;
const int MAXN = 25;
int n,num;
char w[MAXN];
struct Tire
{
	int a[26],c;//字母 & 次数 
	set<int> s;
}t[MAXN*10005];//每个单词最大长度 * 单词数 

int Read()
{
	int x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x<<3) + (x<<1) + (c^48);c = getchar();}
	return x * f;
}
void in(char *w1,int x)//insert
{
	int len = strlen(w1);
	int xb = 0;
	for(int i = 0;i < len;++ i)
	{
		int dz = w1[i] - 'a';
		if(!t[xb].a[dz])
			t[xb].a[dz] = ++num;
		xb = t[xb].a[dz];
		t[xb].c++;
	}
	t[xb].s.insert(x);
}
void Q(char *w1)
{
	int len = strlen(w1);
	int xb = 0;
	for(int i = 0;i < len;++ i)
	{
		int dz = w1[i] - 'a';
		xb = t[xb].a[dz];
		if(t[xb].c == 0)
			return;
	}
	set<int>::iterator it;
	int ans[1005],tot = 0;
	for(it = t[xb].s.begin();it != t[xb].s.end();++ it)
		ans[++tot] = *it;
	for(int i = 1;i < tot;++ i)
		printf("%d ",ans[i]);
	if(tot)
		printf("%d",ans[tot]);
}

int main()
{
	int T = Read();
	for(int wz = 1;wz <= T;++ wz)
	{
		n = Read();
		for(int i = 1;i <= n;++ i)
		{
			scanf("%s",w);
			in(w,wz);
		} 
	}
	int q = Read();
	while(q --)
	{
		scanf("%s",w);
		Q(w);
		if(q)
			putchar('
');
	}
	return 0;
}

最长异或路径代码

//12252024832524
#include <cstdio>
#include <algorithm>
using namespace std; 

typedef long long LL;
const int MAXN = 100005;
const int INF = 2147483647;
int n,tot,ans;
int head[MAXN],xo[MAXN];
struct edge
{
	int v,w,nxt;	
}e[MAXN << 1];
struct trie
{
	int ch[2];
	bool val;
	trie(){ch[0] = ch[1] = 0;}
}t[MAXN * 31];

int Read()
{
	int x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
void Put1(int x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
void Put(int x)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x);
}
template <typename T>T Max(T x,T y){return x > y ? x : y;}
template <typename T>T Min(T x,T y){return x < y ? x : y;}
template <typename T>T Abs(T x){return x < 0 ? -x : x;}

void Add_Edge(int x,int y,int val)
{
	e[++tot].v = y;
	e[tot].w = val;
	e[tot].nxt = head[x];
	head[x] = tot;
}
void dfs(int x,int fa,int s)
{
	xo[x] = s;
	for(int i = head[x]; i ;i = e[i].nxt)
	{
		if(e[i].v == fa) continue;
		dfs(e[i].v,x,s^e[i].w);
	}
}
void ins(int x)
{
	int now = 0;
	for(int i = 30;i >= 0;-- i)
	{
		bool d = (1<<i)&x;
		if(!t[now].ch[d])
			t[now].ch[d] = ++tot;
		now = t[now].ch[d];
	}
}
void solve(int x)
{
	int now = 0,dz = 0;
	for(int i = 30;i >= 0;-- i)
	{
		bool d = (1<<i)&x;
		if(t[now].ch[d^1])
		{
			now = t[now].ch[d^1];
			dz |= (1 << i);
		}
		else
			now = t[now].ch[d];
	}
	ans = Max(ans,dz);
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read();
	for(int i = 1;i < n;++ i)
	{
		int u = Read(),v = Read(),w = Read();
		Add_Edge(u,v,w);
		Add_Edge(v,u,w);
	} 
	dfs(1,0,0);
	tot = 0;
	for(int i = 1;i <= n;++ i)
		ins(xo[i]);
	for(int i = 1;i <= n;++ i)
		solve(xo[i]);
	Put(ans);
	return 0;
}

二、可持续化Trie树

我们可以类比主席树

1.讲解

如果你学懂了普通的(Trie)树,那么我们来学学可持续化的(Trie)

如果你没有学过主席树,没有关系,虽然学过会更好懂一些

我们拿板题来讲

首先我们需要各个元素(第(i)号元素为(a[i]))的异或前缀和,把(i)的前缀和记为(s[i])

(s[i]oplus s[j](i<j))就可以得到(a[i+1])~(a[j])的异或和(基操,勿6,皆坐,观之)

(1).建树 & 插入

我们的具体思路为,对于每个(s[i])建一棵(Trie)树,当然,这个时候就不是字符串了,而是二进制

尽管时间和空间都无法接受

但是我们发现了一个规律(虽然这并没有什么联系):

(s[i])(s[i-1])很明显可以共用很多个节点

对于每个(i),我们当然需要一个根(rt[i])

然后插入(s[i])的时候我们同时跑(i-1)(i)

可以省下很多空间,当然也有时间

(2).查询

首先我们把(X oplus s[n]),记为(x)好了,只需要找(x oplus s[i] (l-1le i le r)),使得(x oplus s[i])最大

怎么搞呢,当然就是贪心啦

我们把(x)的二进制表示的第(i)位记为(x_i)

我们贪心的想,如果使得(x_i)变为(1),一定是最优的,因为如果你(x_i)不为(1),即为(0),那么后面的所有位即使都是(1),加起来也没有(x_i)(1)大了

所以我们要尽量选$x_i oplus s[j]_i=$1,但是怎么判断是否可以这么选呢?

这时我们的(cnt)就不要为结束的标志了,而是每到一个点就(+1)

我们同时跑(l-1)(r),如果(t[r_{now}].cnt - t[(l-1)_{now}].cnt > 0(t)为字典树()),那么就可以选

反之则不行

2.练习

板题(洛谷)

3.代码

板题代码

//12252024832524
#include <cstdio>
#include <algorithm>
using namespace std; 

typedef long long LL;
const int MAXN = 300005;
const int MAXLOG = 23;
int n,m;
int a[MAXN * 2],rt[MAXN * 2];
char opt[2];

int Read()
{
	int x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
void Put1(int x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
void Put(int x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x);
	if(c >= 0) putchar(c);
}
template <typename T>T Max(T x,T y){return x > y ? x : y;}
template <typename T>T Min(T x,T y){return x < y ? x : y;}
template <typename T>T Abs(T x){return x < 0 ? -x : x;}

int tot;
struct trie
{
	int ch[2],cnt;
}t[MAXN * MAXLOG * 2];
void ins(int &x1,int x2,int d,int val)
{
	x1 = ++tot;
	t[x1] = t[x2];
	t[x1].cnt ++;
	if(d == -1) return;
	bool now = val & (1 << d);
	ins(t[x1].ch[now],t[x2].ch[now],d-1,val);
}
int trieq(int x1,int x2,int d,int val)
{
	if(d == -1 || (!x1 && !x2)) return 0;
	bool now = val & (1 << d);
	if(t[t[x1].ch[!now]].cnt > t[t[x2].ch[!now]].cnt)
		return (1 << d) + trieq(t[x1].ch[!now],t[x2].ch[!now],d-1,val);
	else return trieq(t[x1].ch[now],t[x2].ch[now],d-1,val);
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read()+1;
	m = Read();
	for(int i = 2;i <= n;++ i) a[i] = Read() ^ a[i-1];
	for(int i = 1;i <= n;++ i) ins(rt[i],rt[i-1],MAXLOG,a[i]);
	while(m --)
	{
		scanf("%s",opt);
		if(opt[0] == 'A') n++,a[n] = Read() ^ a[n-1],ins(rt[n],rt[n-1],MAXLOG,a[n]);
		else 
		{
			int l = Read(),r = Read();
			Put(trieq(rt[r],rt[l-1],MAXLOG,Read()^a[n]),'
');
		}
	}
	return 0;
}

Update

(2020.05.18),更新了以前没学懂的时候用的指针模板(现已改为非指针)

原文地址:https://www.cnblogs.com/PPLPPL/p/13395243.html