字典树

零、前言

这是一篇高开低走的博客

一、普通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\)\(\text{++}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('\n');
	}
	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-1\le 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]),'\n');
		}
	}
	return 0;
}

Update

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

posted @ 2020-07-29 09:33  皮皮刘  阅读(119)  评论(0编辑  收藏  举报