一本通提高篇 Trie树

前两天熬夜给班级做大合唱视频来的 所以拖了两天
那么我们今天搞这个 t r i e trie trie
不要看书上花里胡哨の一堆图 个人感觉 t r i e trie trie树是个非常简洁的数奆结垢 可以考虑先看博客再看书咳咳
U P D : UPD: UPD:不是前两天 是前两周 累死我了 才开始写
U P D : UPD: UPD:复习期末考试 只有信息课有时间写…
U P D : UPD: UPD: 2020.3.21 2020.3.21 2020.3.21重操旧业
U P D : UPD: UPD:复习一周期中考试…

t r i e trie trie树字典树,也叫字母树,指的是某个字符串集合对应的形如下图所示的有根树。树的每条边上加好对应一个字符,每个顶点代表从根到该点的路径所对应的字符串(将所有经过的边上的字符串按顺序连接起来)。有时我们也称 T r i e Trie Trie上的边为转移,顶点为状态。
顶点还能存储另外的信息,比如可以存储当前节点被遍历了几次。此外,对于任意一个节点,它到它的子节点边上的字符都互不相同。不难发现, T r i e Trie Trie很好的利用了串的公共前缀,节约了储存空间

基操

这是一颗可爱的 T r i e Trie Trie树:

在这个 T r i e Trie Trie里加入的就是 a t , b e e , b e n , b t , q at,bee,ben,bt,q at,bee,ben,bt,q
T r i e Trie Trie有三个小性质:

  1. 根节点是空的,除此之外所有的节点只包含一个字符
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
  3. 每个节点的所有子节点包含的字符都不相同

都是贼**显然然后没啥用的破性质
所以对于插入或者查询每一个长度为 l l l的字符串 复杂度都是 O ( l ) O(l) O(l)

那么怎么实现呢:
1.初始化
     \,\,\,\, 一颗空 T r i e Trie Trie树仅包含一个根节点,该点的字符指针均指向空。
2.插入
     \,\,\,\, 当需要插入一个字符串 S S S时,我们令一个指针 P P P起初指向根节点。然后,依次扫描 S S S中的每一个字符串 c : c: c:
          ( 1 ) \,\,\,\,\,\,\,\,\,(1) (1)若指针 P P P c c c字符指向一个已经存在的节点 Q Q Q,则令 P = Q P=Q P=Q
          ( 2 ) \,\,\,\,\,\,\,\,\,(2) (2)若指针 P P P的字符指针指向空,则新建一个节点 Q Q Q,令 P P P c c c字符指针指向 Q Q Q,然后令 P = Q P=Q P=Q
代码:看下面的例题
个人喜欢用结垢体 书上给的二维数组看着就丑
然后如果有需要就在这里面合理添加条件就可以了

3.查询
     \,\,\,\, 当需要查询一个字符串 S S S T r i e Trie Trie中是否存在时,我们令一个指针 P P P起初指向根节点,然后依次扫描 S S S中的每一个字符 c : c: c:
          ( 1 ) \,\,\,\,\,\,\,\,\,(1) (1)若指针 P P P c c c字符指针指向空,则说明 S S S没有被插过 T r i e Trie Trie,结束查询
          ( 2 ) \,\,\,\,\,\,\,\,\,(2) (2)若指针 P P P c c c字符指针指向一个已经存在的节点 Q Q Q则令 P = Q P=Q P=Q
          ( 3 ) \,\,\,\,\,\,\,\,\,(3) (3)当指针 S S S中的字符扫描完毕时,若当前节点 P P P被标记为一个字符串的末尾,则说明 S S S T r i e Trie Trie中已经存在,否则说明 S S S没有被插入过 T r i e Trie Trie

由于例题的代码不需要记录字符串末尾,所以先上例题吧:
题面
S o l u t i o n : Solution: Solution:果题,没啥可说的,直接 T r i e Trie Trie

#include<bits/stdc++.h>
using namespace std;
#define reg register
struct node{
	int son[26],num;
}a[500500];
char x[55];
int n,m,t;
inline void update(){
	int l=strlen(x),p=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]-'a'])a[p].son[x[i]-'a']=++t;
		p=a[p].son[x[i]-'a'];
	}
}
inline int query(){
	int l=strlen(x),p=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]-'a'])return 0;
		p=a[p].son[x[i]-'a'];
	}
	return ++a[p].num;
}
int main(){
	scanf("%d",&n);
	for(reg int i=1;i<=n;i++){
		scanf("%s",x);
		update();
	}
	scanf("%d",&m);
	for(reg int i=1;i<=m;i++){
		scanf("%s",x);
		int now=query();
		if(!now)puts("WRONG");
		else if(now==1)puts("OK");
		else puts("REPEAT");
	}
}

o k ok ok墨迹完毕 直接上一本通题

Phone List

题面
小声 b b bb bb一句 这题可以存字符串数组然后排序!
嘘 我们还是 T r i e Trie Trie
S o l u t i o n : Solution: Solution:直接 T r i e Trie Trie就行了
如果一个串进来没有加入新的节点或者访问到了一个字符串的结尾 说明存在 A A A B B B的子串
注意两点 : 1. :1. :1.这是数字串 别- ′ a ′ 'a' a − ′ 0 ′ -'0' 0
2. 2. 2.存在输出 N O NO NO,不存在才输出 Y E S . . . YES... YES...

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define N 100100
struct node{
	int pd,son[10];
}a[N];
bool now;
int n,t,cnt;
char x[15];
inline void insert(){
	int l=strlen(x),p=0,flag=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]&15])a[p].son[x[i]&15]=++cnt,flag=1;
		p=a[p].son[x[i]&15];
		if(a[p].pd)now=1;
		if(i==l-1)a[p].pd=1;
	}
	if(!flag)now=1;
}
int main(){
	scanf("%d",&t);
	while(t--){
		memset(a,0,sizeof(a));
		cnt=now=0;
		scanf("%d",&n);
		for(reg int i=1;i<=n;i++){
			scanf("%s",x);
			insert();
		}
		if(!now)puts("YES");
		else puts("NO");
	}
}

P S : PS: PS:我杀洛谷的 U V A UVA UVA慢死了 哥哥可不可以快一点呢~

The XOR Largest Pair

题目描述
给定的n个整数A1,A2,A3…An中选出两个进行异或运算,最后最大结果是多少
输入
第一行一个整数N
第二行N个整数Ai
输出
一个最大结果
样例输入
5
2 9 5 7 0
样例输出
14
提示
n<=1e5,0<=Ai<2^31
S o l u t i o n : Solution: Solution:很容易想到把数字变成二进制放到 T r i e Trie Trie
插入时最好倒序插入 这样方便统计答案
查询时先找找有没有跟这一位不一样的 如果有就跳到那里 没有就跳到跟这一位一样的 同时统计答案
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define N int(1e5+100)
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
int n,a,ans,cnt,son[N*33][2];
inline void insert(int x){
	int now,p=0;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(!son[p][now])son[p][now]=++cnt;
		p=son[p][now];
	}
}
inline int search(int x){
	int now,p=0,ret=0;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(son[p][now^1])ret=ret<<1|1,p=son[p][now^1];
		else ret<<=1,p=son[p][now];
	}
	return ret;
}
int main(){
	read(n);
	for(reg int i=1;i<=n;i++){
		read(a);
		insert(a);
		ans=max(ans,search(a));
	}
	printf("%d\n",ans);
}

Codechef REBXOR

题目描述

输入
输入数据的第一行包含一个整数N,表示数组中的元素个数。
第二行包含N个整数A1,A2,…,AN。
输出
输出一行包含给定表达式可能的最大值。
样例输入
5
1 2 3 1 2
样例输出
6
提示
满足条件的(l1,r1,l2,r2)有:(1,2,3,3),(1,2,4,5),(3,3,4,5)。

对于100%的数据,2 ≤ N ≤ 4*105,0 ≤ Ai ≤ 109

S o l u t i o n : Solution: Solution:
首先我们可以想到 求 i i i~ j j j的异或和 就是求 1 1 1~ j j j的异或和 与 1 1 1~ i i i的异或和的异或和
所以我们只需要维护前缀异或和 求最大区间异或和就变成了求一堆数中两个数的异或和最大 同上题
至于两个区间 那也不难 正着做一遍 反着做一遍 维护点 i i i的左边和右边的区间异或和最大 最后再枚举 i i i即可
上代码

#include<bits/stdc++.h>
using namespace std;
#define N 400040
#define reg register
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
inline void swp(int &x, int &y){x^=y,y^=x,x^=y;}
inline int maxx(int x, int y){return x>y?x:y;}
struct node{
	int son[2];
}a[N*32];
int n,ans,cnt,num[N],s[N],le[N],ri[N];
inline void insert(int x){
	int now,p=0;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(!a[p].son[now])a[p].son[now]=++cnt;
		p=a[p].son[now];
	}
}
inline int search(int x){
	int now,p=0,ret;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(a[p].son[now^1])ret=ret<<1|1,p=a[p].son[now^1];
		else ret<<=1,p=a[p].son[now];
	}
	return ret;
}
int main(){
	read(n);
	for(reg int i=1;i<=n;i++)read(num[i]),s[i]=s[i-1]^num[i];
	for(reg int i=1;i<=n;i++){
		insert(s[i]);
		le[i]+=search(s[i]);
	}
	for(int i=1;i<=n;i++)le[i]=maxx(le[i],le[i-1]);
	memset(a,0,sizeof(a));cnt=0;
	for(reg int i=1;i<=n/2;i++)swp(num[i],num[n-i+1]);
	for(reg int i=1;i<=n;i++){
		s[i]=s[i-1]^num[i];
		insert(s[i]);
		ri[n-i+1]+=search(s[i]);
	}
	for(int i=n;i;i--)ri[i]=maxx(ri[i],ri[i+1]);
	for(reg int i=1;i<=n;i++)ans=maxx(ans,le[i]+ri[i]);
	printf("%d\n",ans);
}

Immediate Decodability

题面
S o l u t i o n : Solution: Solution:这题跟上面第一题一样的啊 直接上代码了啊

#include<bits/stdc++.h>
using namespace std;
#define N 1001
#define reg register
struct node{
	int son[2],pd;
}a[N];
char x[N];
int ans,cnt,now;
inline void insert(){
	int l=strlen(x),p=0,flag=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]&15])a[p].son[x[i]&15]=++cnt,flag=1;
		p=a[p].son[x[i]&15];
		if(a[p].pd)now=1;
		if(i==l-1)a[p].pd=1;
	}
	if(!flag)now=1;
}
int main(){
	while(scanf("%s",x)!=EOF){
		if(x[0]=='9'){
			if(!now)printf("Set %d is immediately decodable\n",++ans);
			else printf("Set %d is not immediately decodable\n",++ans);
			cnt=now=0;
			memset(a,0,sizeof(a));
		}
		else insert();
	}
}

L语言

题面
S o l u t i o n : Solution: Solution:单词直接插入 标记结尾
m a r k mark mark数组代表的时这个文章哪个位置是单词的结尾
查询文章时每查到单词结尾就接着往下标记
过程中维护可以理解的 a n s ans ans的最大值

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define N int(1e6+20)
struct node{
	int son[26];
	bool mk;
}a[2000];
int n,m,cnt;
bool mark[N];
char x[N];
inline void insert(){
	int l=strlen(x),p=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]&15])a[p].son[x[i]&15]=++cnt;
		p=a[p].son[x[i]&15];
	}
	a[p].mk=1;
}
inline void find(){
	memset(mark,0,sizeof(mark));
	int ans=0,p=0,l=strlen(x);
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]&15])break;
		p=a[p].son[x[i]&15];
		if(a[p].mk)mark[i]=1;
	}
	for(reg int i=0;i<l;i++){
		if(!mark[i])continue;
		ans=i+1,p=0;
		for(reg int j=i+1;j<l;j++){
			if(!a[p].son[x[j]&15])break;
			p=a[p].son[x[j]&15];
			if(a[p].mk)mark[j]=1;
		}
	}
	printf("%d\n",ans);
}
int main(){
	scanf("%d%d",&n,&m);
	for(reg int i=1;i<=n;i++){
		scanf("%s",x);
		insert();
	}
	for(reg int i=1;i<=m;i++){
		scanf("%s",x);
		find();
	}
}

Secret Message 秘密信息

题面
S o l u t i o n : Solution: Solution:这破题题面写的奇奇怪怪
总的来说就是 一个密码的答案 包含两个方面
一个是密码经过的秘密信息的结尾的数量
还有就是 是多少个秘密信息的前缀
T r i e Trie Trie树上维护一个点被经过的次数 和被当做结尾的次数就好了
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 500040
#define reg register
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
struct node{
	int son[2],mk,end;
}a[N];
int n,m,k,num,cnt;
inline void insert(int k){
	int now,p=0;
	for(reg int i=0;i<k;i++){
		read(now);
		if(!a[p].son[now])a[p].son[now]=++cnt;
		p=a[p].son[now],a[p].mk++;
	}
	a[p].end++;
}
inline void search(int k){
	int now,p=0,ans=0,f=0;
	for(reg int i=0;i<k;i++){
		read(now);
		if(!a[p].son[now])f=1;
		if(f)continue;
		p=a[p].son[now],ans+=a[p].end;
	}
	if(f)printf("%d\n",ans);
	else printf("%d\n",ans-a[p].end+a[p].mk);
}
int main(){
	read(n),read(m);
	for(reg int i=1;i<=n;i++)read(k),insert(k);
	for(reg int i=1;i<=m;i++)read(k),search(k);
}

背单词

题面
w d n m d wdnmd wdnmd这啥玩意 其实我感觉我要是四五个月前正好玩图论那时候应该是能搞一搞的
这道题我是看完题解再做的
S o l u t i o n ? Solution? Solution?首先要看题面 1 1 1条件一定是没用的 可以避免
2 2 2算是 3 3 3的特殊情况
那么这个题就转化为 一堆字符串 你可以随便排序 其中一个字符串的答案贡献值为 他前面 跟他后缀相同的字符串的距离之和
并求这个答案的最小值
看完这个后缀一定是想着把它都弄成前缀 然后用 T r i e Trie Trie数做
但是问题来了 怎么调整字符串的顺序才能使结果最优呢
肯定是先要建 T r i e Trie Trie树的 用图解释一下


要调整顺序 我们就把单词弄到树上 用单词的结尾点代表一个单词 于是单词结尾前面的信息就没用了 我们只需要记录结尾位置的信息就可以了
所以我们把所有的绿点重构成一颗树 也就是把树改成一个由单词结尾节点的树
那么怎么排呢 这里使用 d f s dfs dfs序 为什么呢
这个博客里说的实在是太好了 我就搬过来吧
在这里插入图片描述
就是这样
上代码

#include<bits/stdc++.h>
using namespace std;
#define L 520000
#define ll long long
#define reg register
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
vector<int> edge[L];
//建字典树 
struct trie{
	int son[26];
	bool tag;
}a[L];
int cnt;
char x[L];
inline void insert(){
	int l=strlen(x),p=0;
	for(reg int i=0;i<l;i++){
		if(!a[p].son[x[i]-'a'])a[p].son[x[i]-'a']=++cnt;
		p=a[p].son[x[i]-'a'];
	}
	a[p].tag=1;
}
//开始c作
//建树 
int lst[L];
void sett(int x){
	if(a[x].tag&&x)edge[lst[x]].push_back(x),lst[x]=x;
	for(reg int i=0;i<26;i++)
		if(a[x].son[i])
			lst[a[x].son[i]]=lst[x],sett(a[x].son[i]);
}
//把树重新排序 
int siz[L];
inline bool cmp(const int &x, const int &y){
	return siz[x]<siz[y];
}
void dfs(int x){
	siz[x]=1;
	for(reg int i=0;i<edge[x].size();i++){
		dfs(edge[x][i]);
		siz[x]+=siz[edge[x][i]];
	}
	sort(edge[x].begin(),edge[x].end(),cmp);
}
//按照dfs序统计答案
ll ans;
int sum;//dfn定义在外面会WA 
void getans(int x){
	int dfn=sum++;
	for(reg int i=0;i<edge[x].size();i++){
		ans+=(ll)sum-dfn;
		getans(edge[x][i]);
	}
}
int n;
int main(){
	read(n);
	for(reg int i=1;i<=n;i++){
		scanf("%s",x);
		reverse(x,x+strlen(x));
		insert();
	}
	a[0].tag=1;
	sett(0),dfs(0),getans(0);
	printf("%lld\n",ans);
}

注意: 1. 1. 1.根也是绿点 不要忘了根 要不你会很惨
2. 2. 2.答案要开 l o n g   l o n g long\,long longlong
3. 3. 3.算时间戳的时候 d f n dfn dfn不要定义全局变量…

The xor-longest Path

题面
这不跟前面题一样嘛…
S o l u t i o n : Solution: Solution: d [ x ] d[x] d[x] d d d到根节点的异或和
对于 i i i j j j的路径的异或和 = ( d [ x ] =(d[x] =(d[x]^ d [ l c a ] ) d[lca]) d[lca]) ^ ( d [ y ] (d[y] (d[y] ^ d [ l c a ] ) d[lca]) d[lca])
= d [ x ] =d[x] =d[x]^ d [ y ] d[y] d[y] 那不就是一堆数求两个点异或最大嘛
直接上代码了

#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define reg register
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
struct node{
	int to,val,nxt;
}edge[N<<1];
int tot,d[N],head[N];
inline void addedge(int u, int v, int w){
	edge[++tot].to=v,edge[tot].val=w,edge[tot].nxt=head[u],head[u]=tot;
}
inline void superadd(int u, int v, int w){
	addedge(u,v,w),addedge(v,u,w);
}
void dfs(int u, int fa){
	for(reg int i=head[u];i;i=edge[i].nxt){
		int vv=edge[i].to;
		if(vv==fa)continue;
		d[vv]=d[u]^edge[i].val;
		dfs(vv,u);
	}
}
struct trie{
	int son[2];
}a[N*32];
int n,u,v,w,ans,cnt;
inline void insert(int x){
	int now,p=0;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(!a[p].son[now])a[p].son[now]=++cnt;
		p=a[p].son[now];
	}
}
inline int search(int x){
	int now,p=0,ret=0;
	for(reg int i=31;~i;i--){
		now=(x>>i)&1;
		if(a[p].son[now^1])ret=ret<<1|1,p=a[p].son[now^1];
		else ret<<=1,p=a[p].son[now];
	}
	return ret;
}
int main(){
	read(n);
	for(reg int i=1;i<n;i++)
		read(u),read(v),read(w),superadd(u,v,w);
	dfs(1,0);
	for(reg int i=1;i<=n;i++)insert(d[i]);
	for(reg int i=1;i<=n;i++)ans=max(search(d[i]),ans);
	printf("%d\n",ans);
}

P S . PS. PS.我吐了 我数组多开了个 0    b z 0\,\,bz 0bz判我 T L E TLE TLE看了十分钟…
总结: T r i e Trie Trie树是个解决字符串前缀问题非常好的工具 在二进制问题上也非常实用 搭配其他数据结构总能得到意想不到的效果
有问题可以留到评论区或者加 Q Q 407694747 QQ407694747 QQ407694747 我们一起讨论
喜欢的话可以素质三连支持一波不好意思拿错台词了
喜欢的朋友们可以给个赞哦 爱您
下一篇 A C AC AC自动机!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值