前两天熬夜给班级做大合唱视频来的 所以拖了两天
那么我们今天搞这个 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
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墨迹完毕 直接上一本通题
题面
小声 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
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慢死了 哥哥可不可以快一点呢~
题目描述
给定的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
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);
}
题目描述
输入
输入数据的第一行包含一个整数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
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);
}
题面
S o l u t i o n : Solution: Solution:这题跟上面第一题一样的啊 直接上代码了啊
#include
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();
}
}
题面
S o l u t i o n : Solution: Solution:单词直接插入 标记结尾
m a r k mark mark数组代表的时这个文章哪个位置是单词的结尾
查询文章时每查到单词结尾就接着往下标记
过程中维护可以理解的 a n s ans ans的最大值
#include
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();
}
}
题面
S o l u t i o n : Solution: Solution:这破题题面写的奇奇怪怪
总的来说就是 一个密码的答案 包含两个方面
一个是密码经过的秘密信息的结尾的数量
还有就是 是多少个秘密信息的前缀
T r i e Trie Trie树上维护一个点被经过的次数 和被当做结尾的次数就好了
上代码↓
#include
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
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不要定义全局变量…
题面
这不跟前面题一样嘛…
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
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自动机!