按照一本通往下学,学到吐血了。。。

例题1

字典树模板题吗。

先讲讲字典树:

给出代码(太简单了。。。)!

#include<cstdio>
#include<cstring>
using namespace std;
struct trie
{
int a[10],v;
//a代表0~9的儿子的编号,有则为这个节点的编号,没有就是0。
//v是附加权值,代表每个节点被经过的次数。
}tr[200000];int trlen;//多少节点
char st[11000][20];int n;
void add(char ss[])
{
int len=strlen(ss+1),root=0;//root代表根节点
for(int i=1;i<=len;i++)
{
int k=ss[i]-'0';//计算是哪个儿子。。。
if(tr[root].a[k]==0)tr[root].a[k]=++trlen;//添加新节点
root=tr[root].a[k];tr[root].v++;//给这个节点加一次走过的标记
}
}
int find(char ss[])
{
int len=strlen(ss+1),root=0;//root代表根节点
for(int i=1;i<=len;i++)//找到这个字符串最底下的节点编号
{
int k=ss[i]-'0';
root=tr[root].a[k];
}
return tr[root].v-1;//被经过两次代表这个字符串是某个字符串的前缀。
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
memset(tr,0,sizeof(tr));trlen=0;//初始化
scanf("%d",&n);
for(int i=1;i<=n;i++)//添加
{
scanf("%s",st[i]+1);
add(st[i]);
}
bool bk=false;
for(int i=1;i<=n;i++)
{
if(find(st[i]))//发现答案
{
bk=true;
printf("NO\n");//输出
break;
}
}
if(bk==false)printf("YES\n");//同样是输出。
}
return 0;
}

例题二

我们发现只要把每个数字转成31位二进制,然后,一个个插入字典树,在插入之前,我们计算一下当前的数与哪个数的异或值最大,并记录一下。

至于如何找,只需要,我们只需要每次走与当前二进制位数不一样的数字就行了,看代码还挺好理解的(find函数)

#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
int a[2],v;//v是附加权值,代表这个二进制数是多少
}tr[6100000];int trlen;
char st[40];
int n,ans;
void lintoto(int x)//将x转成二进制数
{
memset(st,0,sizeof(st));
int len=0;
while(x)
{
st[++len]=(x&1);//相当于x%2。
x>>=1;//除于2
}
}
void add(char ss[],int id)
{
int len=31,root=0;
for(int i=len;i>=1;i--)//由于当时存二进制是倒着存的,现在也要倒着搜
{
int k=ss[i];
if(!tr[root].a[k])tr[root].a[k]=++trlen;//新建节点
root=tr[root].a[k];
}
tr[root].v=id;//添加附加权值
}
inline int mymax(int x,int y){return x>y?x:y;}
inline int find(char ss[],int id)
{
int root=0,len=31;
for(int i=len;i>=1;i--)
{
int k=ss[i];
if(tr[root].a[k^1])root=tr[root].a[k^1];//走相反的地方
else root=tr[root].a[k];//不存在,走相同的地方。
}
return (tr[root].v^id);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
lintoto(x);//转成二进制
if(i!=1)ans=mymax(ans,find(st,x));//更新新的答案
add(st,x);//添加
}
printf("%d\n",ans);
return 0;
}

例题三

先证明异或符合交换律、结合律。

首先,异或的过程中:(1,0),(0,1),(0,0)都是消掉一个0,而(1,1)是消掉一个1。

我们可以发现,多次异或就是看所有数二进制的每一位的1的个数,奇数就为1,偶数为0,而你调换顺序的话,个数并没有变,所以满足交换律与结合律。

那么这道题,我们设\(sst_{i}=a_{1}⨁a_{2}⨁a_{3}⨁a_{4}...⨁a_{i}\),那么,求区间最大,我们可以发现:

\(a_{l}⨁a_{l+1}⨁...⨁a_{r}=(a_{1}⨁a_{2}⨁a_{3}⨁...⨁a_{r})⨁(a_{1}⨁a_{2}⨁a_{3}⨁...⨁a_{l-1})=sst_{r}⨁sst_{l-1}\)

也就是说,我们只要求出在1~r-1区间内能异或\(sst_{r}\)的最大值,也就是上面那道题,设\(ll_{i}=\) 在1~i区间内的一个区间最大异或值,\(ll_{i}=max(ll_{i-1},以i为结尾的最大异或区间)\)。

再设一个\(rr_{i}=\) 在i~n区间内的一个区间最大异或值,\(rr_{i}=max(rr_{i+1},以i为开头的最大异或区间)\)。

求出最大的\(ll_{i-1}+rr_{i}\)

#include<cstdio>
#include<cstring>
using namespace std;
struct trie
{
int a[2],v;
}tr[9100000];int trlen;//字典树
char st[210];
int sst[410000],n,ll[410000],rr[410000],ans;//数组
void intto(int x)//像上次那样。。。
{
memset(st,0,sizeof(st));
int len=0;
while(x)
{
st[++len]=(x&1);//x%2
x>>=1;//x/=2
}
}
void add(char ss[],int id)//添加
{
int len=31,root=0;
for(int i=len;i>=1;i--)
{
int k=ss[i];
if(!tr[root].a[k])tr[root].a[k]=++trlen;
root=tr[root].a[k];
}
tr[root].v=id;
}
int find(char ss[],int id)//寻找
{
int len=31,root=0;
for(int i=len;i>=1;i--)
{
int k=ss[i];
if(tr[root].a[k^1])root=tr[root].a[k^1];
else root=tr[root].a[k];
}
return (tr[root].v^id);//返回异或值
}
inline int getsum(int x,int y){return sst[y]^sst[x-1];}//[x,y]区间的异或值
inline int mymax(int x,int y){return x>y?x:y;}//最大值
int main()
{
scanf("%d",&n);
intto(0);
add(st,0);//将0添加
for(int i=1;i<=n;i++)
{
scanf("%d",&sst[i]);sst[i]^=sst[i-1];
intto(sst[i]);//二进制
ll[i]=mymax(find(st,sst[i]),ll[i-1]);//更新
add(st,sst[i]);//添加
}
memset(tr,0,sizeof(tr));trlen=0;
intto(0);
add(st,0);
for(int i=n;i>=1;i--)
{
intto(getsum(i,n));
rr[i]=mymax(rr[i-1],find(st,getsum(i,n)));
add(st,getsum(i,n));
}//反着来一遍
for(int i=2;i<=n;i++)ans=mymax(ans,ll[i-1]+rr[i]);//统计答案
printf("%d\n",ans);
return 0;
}

练习一

跟例题一 一样。。。只不过处理方式出了点问题。

#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
struct node
{
int a[10],v;
}tr[2100];int trlen;//字典树
char st[20];
void add(char ss[])//打得不能再顺手的添加。。。
{
int len=strlen(ss+1),root=0;
for(int i=1;i<=len;i++)
{
int k=ss[i]-'0';
if(!tr[root].a[k])tr[root].a[k]=++trlen;
root=tr[root].a[k];
}
tr[root].v=1;
}
bool find(char ss[])//每天都在变。。。
{
int len=strlen(ss+1),root=0;
for(int i=1;i<=len;i++)
{
int k=ss[i]-'0';
if(!tr[root].a[k])return false;//不存在节点?返回
root=tr[root].a[k];
if(tr[root].v==1)return true;//存在,返回
}
return false;
}
int main()
{
int T=0;
while(scanf("%s",st+1)!=EOF)
{
T++;
memset(tr,0,sizeof(tr));trlen=0;//初始化
bool bk=false;
while(1)
{
int len=strlen(st+1);
if(len==1 && st[1]=='9')break;//退出
if(find(st))bk=true;//寻找
add(st);//添加
scanf("%s",st+1);//输入
}
if(bk==true)printf("Set %d is not immediately decodable\n",T);
else printf("Set %d is immediately decodable\n",T);
}
return 0;
}

练习二

将字典建个字典树,然后类似递推思想瞎搞。。。

提示:建一个d数组代表这一段在前i位能否被表达。

#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
int a[26],v;
}tr[51000];int trlen;//字典树
char st[2100000];//字符串
int n,m;
char d[2100000];//d数组
void add(char ss[])
{
int len=strlen(st+1),root=0;
for(int i=1;i<=len;i++)
{
int k=st[i]-'a';
if(!tr[root].a[k])tr[root].a[k]=++trlen;
root=tr[root].a[k];
}
tr[root].v=1;
}
bool find(int l,int r)//匹配l~r区间
{
int root=0;
for(int i=l;i<=r;i++)
{
int k=st[i]-'a';
if(!tr[root].a[k])return false;
root=tr[root].a[k];
}
return tr[root].v;
}
inline int mymax(int x,int y){return x>y?x:y;}
int jie;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%s",st+1);
add(st);
jie=mymax(jie,strlen(st+1));//最长的单词
}
for(int kkk=1;kkk<=m;kkk++)
{
memset(d,0,sizeof(d));d[0]=1;//初始化
scanf("%s",st+1);
int len=strlen(st+1);
for(int i=1;i<=len;i++)
{
for(int j=mymax(1,i-jie+1);j<=i;j++)
{
if(d[j-1]==1 && find(j,i))//如果前j-1位已经匹配成功,那么判断当前能否匹配成功
{
d[i]=1;
break;
}
}
}
int ans=0;
for(int i=len;i>=1;i--)//统计
{
if(d[i]==1)
{
ans=i;break;
}
}
printf("%d\n",ans);
}
return 0;
}

练习三

建立信息为字典树,两个附加权值,代表被经过的次数和作为结尾的次数。

那密码进行统计。。。

怎么统计自己想或看代码

#include<cstdio>
#include<cstring>
using namespace std;
struct trie
{
int a[2],v1,v2;//两个附加权值,v1代表被经过次数,v2代表被作为结尾的次数
}tr[610000];int trlen;//字典树
char st[510000];//字符串
int n,m;
void add(int len)//添加
{
int root=0;
for(int i=1;i<=len;i++)
{
int k=st[i];
if(!tr[root].a[k])tr[root].a[k]=++trlen;
root=tr[root].a[k];tr[root].v1++;//经过一次
}
tr[root].v2++;//作为结尾加一
}
int find(int len)
{
int root=0,ans=0;
for(int i=1;i<=len;i++)
{
int k=st[i];
if(!tr[root].a[k])return ans;
root=tr[root].a[k];ans+=tr[root].v2;//有多少是他的前缀
}
ans+=tr[root].v1-tr[root].v2;//他是多少字符串的后缀
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int len;scanf("%d",&len);
for(int j=1;j<=len;j++)scanf("%d",&st[j]);//输入字符串
add(len);//添加
}
for(int i=1;i<=m;i++)
{
int len;scanf("%d",&len);
for(int j=1;j<=len;j++)scanf("%d",&st[j]);//输入字符串
printf("%d\n",find(len));//寻找
}
return 0;
}

练习四

这道题目QAQ。

首先,我们知道,长度为s1的字符串a1,长度为s2的字符串a2,如果\(s1<s2\)并且a1与a2都是a3的后缀,那么a1是a2的后缀。

将所有字符串翻转,建一个字典树,然后建一颗树,当\(i\)->\(j\)仅当,\(a_{i}\)是\(a_{j}\)的最大前缀。

然后,DFS新树,每次进入没进过且节点最少的树就行了。

(如果直接在字典树上跑的话,子树大小会错乱,导致每次进的不是节点最小的树)

这道题还是有点难的,因为用到了一点贪心思想。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char st[520000];
int n;
bool bol[110000];
struct trie//字典树
{
int a[26],v;
}tr1[610000];int trlen1;
struct node//新建的树
{
int y,next;
}tr2[110000];int trlen2,last[110000],size[110000];//用边目录储存
void ins(int x,int y)//新建一条边
{
trlen2++;
tr2[trlen2].y=y;tr2[trlen2].next=last[x];last[x]=trlen2;
}
void add(int id)//添加
{
int root=0,len=strlen(st+1);
for(int i=len;i>=1;i--)//字符串翻转
{
int k=st[i]-'a';
if(!tr1[root].a[k])tr1[root].a[k]=++trlen1;
root=tr1[root].a[k];
}
tr1[root].v=id;
}
int dfss(int x,int fa)//遍历字典树,建新树,fa是离他最近的有权的祖先
{
int ans=0;//代表子树大小
for(int i=0;i<=25;i++)
{
if(tr1[x].a[i])
{
if(tr1[x].v)ans+=dfss(tr1[x].a[i],tr1[x].v);//本身是一个节点
else ans+=dfss(tr1[x].a[i],fa);//本身不是一个节点,只是一个中转站
}
}
if(tr1[x].v)//本身是一个节点
{
ins(fa,tr1[x].v);//连向父亲
ans++;size[tr1[x].v]=ans;//继承大小
}
return ans;//返回目前的子树大小
}
long long anss;
int times,lis[110000],listlen;
bool cmp(int x,int y){return size[x]<size[y];}
void dfs(int x)
{
int now=++times;
int l=listlen+1,r;
for(int k=last[x];k;k=tr2[k].next)lis[++listlen]=tr2[k].y;//处理list
r=listlen;
if(l<=r)sort(lis+l,lis+1+r,cmp);//排序是排[l,r)
else return ;//没有子树
for(int i=l;i<=r;i++)
{
anss+=(times+1)-now;//统计答案
dfs(lis[i]);//继续递归
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",st+1);
add(i);
}//添加所有字符串
dfss(0,0);//建树
dfs(0);//处理答案
printf("%lld\n",anss);//输出
return 0;
}

练习五

这道题,我一开始想得太复杂了。。。

我一开始想到的是一条路径上,起点是l,终点是r,然后还涉及到求LCA,然后就十分复杂。。。

然后膜了一波题解,发现自己就是个弱智。。。

设\(sum[i]=(1->i)\)的权值异或值 ,那么一条路径上的异或权值和就是\(sum[l]\)^\(sum[j]\),然后就是求一个数组中求与\(sum[i]\)异或值最大的数,其实就是例题了,根本不用在树上进行特别麻烦的遍历,或者是过程很麻烦的树上递归,是不是很巧妙?好吧,我承认就是我没有想到然后就乱膜。。。

#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
int y,next,c;
}a[210000];int last[110000],alen;
int sum[110000];//边目录
void ins(int x,int y,int c)
{
alen++;
a[alen].y=y;a[alen].c=c;a[alen].next=last[x];last[x]=alen;
}
void dfs(int x,int fa)
{
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(y!=fa)//不是他的父亲
{
sum[y]=sum[x]^a[k].c;//处理sum数组
dfs(y,x);//继续递归
}
}
}
struct trie
{
int a[2],v;
}tr[4100000];int trlen;//字典树
char st[40];//储存二进制的数组。
void intoo(int x)
{
memset(st,0,sizeof(st));//初始化
int len=0;
while(x)
{
st[++len]=(x&1);//x%2
x>>=1;//x/=2
}
}
void add(int id)//添加
{
int len=31,root=0;
for(int i=len;i>=1;i--)//添加二进制要注意的
{
int k=st[i];
if(!tr[root].a[k])tr[root].a[k]=++trlen;
root=tr[root].a[k];
}
tr[root].v=id;
}
int find(int id)
{
if(trlen==0)return 0;//判断目前字典树是否有节点
int len=31,root=0;
for(int i=len;i>=1;i--)
{
int k=st[i];
if(tr[root].a[k^1])root=tr[root].a[k^1];//贪心找异或值最大
else root=tr[root].a[k];
}
return (id^tr[root].v);//给出结果
}
int n;
inline int mymax(int x,int y){return x>y?x:y;}//找最大值
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y,c;scanf("%d%d%d",&x,&y,&c);
ins(x,y,c);ins(y,x,c);
}
dfs(1,0);//默认1为根,处理sum数组
int ans=0;
for(int i=1;i<=n;i++)//例题的做法
{
intoo(sum[i]);
ans=mymax(ans,find(sum[i]));
add(sum[i]);
}
printf("%d\n",ans);
return 0;
}

终于写完了。。。

字典树(Trie)的学习笔记的更多相关文章

  1. 字典树trie的学习与练习题

    博客详解: http://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html http://eriol.iteye.com/bl ...

  2. 字典树trie学习

    字典树trie的思想就是利用节点来记录单词,这样重复的单词可以很快速统计,单词也可以快速的索引.缺点是内存消耗大 http://blog.csdn.net/chenleixing/article/de ...

  3. [POJ] #1002# 487-3279 : 桶排序/字典树(Trie树)/快速排序

    一. 题目 487-3279 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 274040   Accepted: 48891 ...

  4. 『字典树 trie』

    字典树 (trie) 字典树,又名\(trie\)树,是一种用于实现字符串快速检索的树形数据结构.核心思想为利用若干字符串的公共前缀来节约储存空间以及实现快速检索. \(trie\)树可以在\(O(( ...

  5. 珂朵莉树(Chtholly Tree)学习笔记

    珂朵莉树(Chtholly Tree)学习笔记 珂朵莉树原理 其原理在于运用一颗树(set,treap,splay......)其中要求所有元素有序,并且支持基本的操作(删除,添加,查找......) ...

  6. 字典树(Trie)详解

    详解字典树(Trie) 本篇随笔简单讲解一下信息学奥林匹克竞赛中的较为常用的数据结构--字典树.字典树也叫Trie树.前缀树.顾名思义,它是一种针对字符串进行维护的数据结构.并且,它的用途超级广泛.建 ...

  7. 字典树(Trie)学习笔记

    目录 什么是字典树 如何存储字典树 如何查找字符串有没有出现 第一个图的那种线段树 应用 例题 1.统计难题 2.P2580 于是他错误的点名开始了 什么是字典树 上图来自luogu题解 这是一种字典 ...

  8. 字典树 trie树 学习

    一字典树 字典树,又称单词查找树,Trie树,是一种树形结构,哈希表的一个变种   二.性质 根节点不包含字符,除根节点以外的每一个节点都只包含一个字符: 从根节点到某一节点,路径上经过的字符串连接起 ...

  9. LeetCode 14. Longest Common Prefix字典树 trie树 学习之 公共前缀字符串

    所有字符串的公共前缀最长字符串 特点:(1)公共所有字符串前缀 (好像跟没说一样...) (2)在字典树中特点:任意从根节点触发遇见第一个分支为止的字符集合即为目标串 参考问题:https://lee ...

随机推荐

  1. Siebel Tools配置

    默认安装的Siebel+Tools,Tools登陆时有3个选项:Local.Sample.Server,具体涵义如下: Local:指本地数据库.按照Siebel开发建议,开发人员需要从Siebel ...

  2. 使用版本 1.0.0 的 Azure ARM SDK for Java 创建虚拟机时报错

    问题描述 我们可以通过使用 Azure ARM SDK 来管理 Azure 上的资源,因此我们也可以通过 SDK 来创建 ARM 类型的虚拟机,当我们使用 1.0.0 版本的 Azure SDK fo ...

  3. jQuery 资料

    jQuery 元素选择器 jQuery 使用 CSS 选择器来选取 HTML 元素. $("p") 选取 <p> 元素. $("p.intro") ...

  4. March 3 2017 Week 9 Friday

    Each time you love, love as deeply as if it were forever. 如果爱,请深爱,就像能到地老天荒. If we can only encounter ...

  5. Android(java)学习笔记55:LayoutInflater 和 findViewById

    1. 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById(). 不同点是LayoutInflater是用来找res/layout/下的xml布局文件, ...

  6. ACM-ICPC(10/21)

    写一发后缀数组套路题,看起来简单,写起来要人命哦~~~ 总共13题. 分两天debug吧,有点累了~~~ suffix(后缀数组的应用) sa[i] :排名第 i 的后缀在哪(i 从 1 开始) ra ...

  7. POJ 2942 圆桌骑士

    之前做过这个题目,现在回想起来,又有新的柑橘. 求必须出去的骑士人数. 每一个双连通分量,如果是一个奇圈,那么一定是二分图染色失败. 依次遍历每个双连通分量,但是,对于邻接表中,有一些点不是双连通分量 ...

  8. Gym 101334C 无向仙人掌

    给出图,求他的“仙人掌度”,即求包括他自身的生成子图有多少? 只能删去仙人掌上的叶子的一条边,然后根据乘法原理相乘: 1.怎么求一个仙人掌叶子上有多少边? 可以利用点,边双连通的时间戳这个概念,但是绝 ...

  9. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(七)之 图文,附件消息(2016-05-05 12:13)

    上一篇介绍了加好友的流程,这里不再赘述,不过之前的聊天只能发送普通文字,那么本篇就教你如何实现发送附件和图片消息.我们先对功能进行分析: 发送图片,附件,需要实现上传图片和附件的功能. textare ...

  10. 【luogu P1402 酒店之王】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1402 菜 #include <queue> #include <cstdio> #i ...