前两天熬夜给班级做大合唱视频来的 所以拖了两天
那么我们今天搞这个
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有三个小性质:
- 根节点是空的,除此之外所有的节点只包含一个字符
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
都是贼**显然然后没啥用的破性质
所以对于插入或者查询每一个长度为
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自动机!