zoukankan      html  css  js  c++  java
  • Trie树学习总结

    Trie

    简要说明

    • (Trie) 树在多个串的检索中十分实用,也是自动机的基础.
    • 它可以将若干个字符串组合成一颗树,不少题可以建好 (Trie) 树后在上面树形 (dp) .
    • 另外一个应用是解决最大异或值的问题,将数看成 (01) 串,贪心地在 (Trie) 树上走.

    题目

    Phone List

    • 题意:给若干数字串,问有没有两个串满足一个串是另一个串的前缀.
    • 将读入的串一个个插入 (Trie) 树中,不难发现串 (A) 是串 (B) 的前缀,只有两种情况.
    • 一种情况是串 (A) 先被插入,那么在插入串 (B) 时,一定会经过 (A) 的单词节点.
    • 另一种情况是串 (B) 先被插入,那么在插入串 (A) 时,都是沿着 (B) 的边走的,并没有新建边.
    • 一边插入,一边判断这两种情况是否出现即可.
    • 另一个简单而有效的做法是(hash).将出现过的前缀全部用 (vis) 数组标记即可.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=5e5+10;
    const int Siz=15;
    int tcnt=0;
    struct Trie{
    	inline int idx(char c)
    		{
    			return c-'0';
    		}
    	int ch[MAXN][Siz];
    	int val[MAXN];
    	int cnt;
    	void init()
    		{
    			memset(ch,0,sizeof ch);
    			cnt=0;
    		}
    	int ins(char *s)
    		{
    			int n=strlen(s);
    			int u=0;
    			int f=0;
    			for(int i=0;i
    
    

    The XOR Largest Pair

    • 题意:给若干个整数,求出两两异或能得到的最大值.
    • 使用 (Trie) 树,将每个数看做一个长度为 (32) 的字符串插入 (Trie) 树中.
    • 插入完毕后,枚举一个数,在 (Trie) 树中往下走,尽量使每步的 (0/1) 和枚举的数都不一样.
    • 这样贪心取即可求出答案.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e6+10;
    const int MAXL=31;
    struct Trie{
    	int idx;
    	int ch[10*MAXN][2];
    	Trie()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    		}
    	void ins(int x)
    		{
    			int u=0;
    			for(int i=MAXL;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    		}
    	int maxxor(int x)
    		{
    			int u=0;
    			int res=0;
    			for(int i=MAXL;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(ch[u][k^1])
    						{
    							res<<=1;
    							res+=1;
    							u=ch[u][k^1];		
    						}
    					else
    						{
    							res<<=1;
    							u=ch[u][k];
    						}	
    				}
    			return res;
    		}
    }T;
    int a[MAXN];
    int main()
    {
    	int n=read();
    	for(int i=1;i<=n;++i)
    		{
    			a[i]=read();
    			T.ins(a[i]);
    		}
    	int ans=0;
    	for(int i=1;i<=n;++i)
    		ans=max(ans,T.maxxor(a[i]));
    	printf("%d
    ",ans);
    	return 0;
    }
    

    Nikitosh 和异或

    • 题意:给出 (n) 个整数,选出两个严格不相交的区间 (A=[l_1,r_1],B=[l_2,r_2]) ,使得 (A) 的异或和加上 (B) 的异或和最大,求出这个最大值.
    • 若记录前缀异或和 (s) ,根据异或的性质,(x xor x =0,)(a[l_1] xor a[l_1+1] xor ......a[r_1]=s[r_1] xor s[l_1-1].)
    • 可以记(l[i])表示在(i)左侧选出区间的最大异或值,(r[i])表示在(i)右侧选出区间的最大异或值.
    • 将前/后缀依次插入 (Trie) 中,找出最大异或和即可.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e6+10;
    const int MAXL=31;
    struct Trie{
    	int idx;
    	int ch[10*MAXN][2];
    	void ins(int x)
    		{
    			int u=0;
    			for(int i=MAXL;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    		}
    	int maxxor(int x)
    		{
    			int u=0;
    			int res=0;
    			for(int i=MAXL;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(ch[u][k^1])
    						{
    							res<<=1;
    							res+=1;
    							u=ch[u][k^1];		
    						}
    					else
    						{
    							res<<=1;
    							u=ch[u][k];
    						}	
    				}
    			return res;
    		}
    	void init()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			ins(0);//避免1个数求不出的情况 
    		}
    	Trie()
    		{
    			init();
    		}
    }T;
    int a[MAXN],n;
    int l[MAXN],r[MAXN];//i两侧的最大值 
    int main()
    {
    	n=read();
    	int cur=0;
    	for(int i=1;i<=n;++i)
    		{
    			a[i]=read();
    			cur^=a[i];
    			T.ins(cur);
    			l[i]=max(l[i-1],T.maxxor(cur));
    		}
    	cur=0;
    	T.init();
    	for(int i=n;i>=1;--i)
    		{
    			cur^=a[i];
    			T.ins(cur);
    			r[i]=max(r[i+1],T.maxxor(cur));
    		}
    	int ans=0;
    	for(int i=1;i
    
    

    L 语言

    • 题意:给出若干个单词和若干篇文章.对于每篇文章,求出它能被完全匹配的最大前缀长度.
    • 将单词全部插入 (Trie) 树中.用(f[i])表示前缀(1)(i) 能否被单词完全匹配.
    • 匹配文章时,应先连好失配边(所以这其实是一个 (AC) 自动机的题对吧).
    • 若当前匹配到了单词结点,且这个单词的开头的前面都能被完全匹配,显然这个位置的前缀可以被完全匹配,更新答案.
    • 在插入单词的时候将权值 (val) 设置为单词长度记录下来即可.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e6+10;
    const int Siz=26;
    char buf[MAXN];
    struct Trie{
    	int ch[1000][Siz];
    	int val[1000];
    	int fail[MAXN];
    	int idx;
    	int f[MAXN];
    	Trie()
    		{
    			memset(ch,0,sizeof ch);
    			memset(fail,0,sizeof fail);
    			idx=0;
    		}
    	void ins(char *s)
    		{
    			int n=strlen(s);
    			int u=0;
    			for(int i=0;i q;
    			q.push(0);
    			while(!q.empty())	
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i
    
    

    Secret Messages

    • 题意:给出 (N) 个信息串, (M) 个密码串.对于每个密码串,回答有几个信息串与它满足一个是另一个的前缀.
    • 将信息串插入 (Trie) 树中,记录子树大小.询问时,是密码串前缀的,在搜索过程中会经过,密码串是它的前缀的,到最后一个字符处加上子树大小即可.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=5e5+10;
    struct Trie{
    	int idx;
    	int ch[MAXN][2];
    	int siz[MAXN];//计算自身的子树大小 
    	int val[MAXN];
    	Trie()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			memset(siz,0,sizeof siz);
    			memset(val,0,sizeof val);
    		}
    	void ins(int n)
    		{
    			int u=0;
    			for(int i=1;i<=n;++i)
    				{
    					int k=read();
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			++val[u];
    		}
    	void dfs(int u)
    		{
    			siz[u]=val[u];
    			for(int i=0;i<=1;++i)
    				if(ch[u][i])
    					{
    						dfs(ch[u][i]);
    						siz[u]+=siz[ch[u][i]];
    					}
    		}
    	int solve(int n)
    		{
    			int u=0,res=0,flag=0;
    			for(int i=1;i<=n;++i)
    				{
    					int k=read();
    					if(!ch[u][k])
    						flag=1;
    					if(flag)
    						continue;
    					u=ch[u][k];
    					if(i!=n)
    						res+=val[u];
    					else
    						res+=siz[u];
    				}
    			return res;
    		}
    }T;
    int main()
    {
    	int n=read(),m=read();
    	for(int i=1;i<=n;++i)
    		{
    			int len=read();
    			T.ins(len);			
    		}
    	T.dfs(0);
    	for(int i=1;i<=m;++i)
    		{
    			int len=read();
    			int ans=T.solve(len);
    			printf("%d
    ",ans);
    		}
    	return 0;
    }
    

    背单词

    • 题意:给你 (n) 个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串 (s) 的位置为 (x) ):

      • 1.排在 (s) 后面的字符串有 (s) 的后缀,则代价为$ n^2$ ;

      • 2.排在 (s) 前面的字符串有 (s) 的后缀,且没有排在 (s) 后面的 (s) 的后缀,则代价为 (x-y)(y) 为最后一个与 (s) 不相等的后缀的位置);

      • 3.(s) 没有后缀,则代价为(x)

      求最小代价和。

    • 将所有字符串翻转插入一颗 (Trie) 树中,则关于后缀的问题全部转化为前缀.

    • 容易发现,我们一定可以避免 (1) 情况的出现( (DAG) 图拓扑排序),且避免后代价一定更优.最坏也只有(frac{n(n+1)}{2} < n^2).若在位置 (0) 加上一个虚拟根作为所有字符串的前缀,那么情况 (3) 可以看做是 (2) 的特殊情况,可以一起处理.

    • 将每个字符串作为一个节点,给节点编号,求节点编号与父亲编号差的最小值即可.

    • 贪心地做, (dfs) 按照子树大小从小到大选出来先编号即可.

    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXL=510010;
    const int Siz=26;
    const int MAXN=1e5+10;
    int tot=0;
    int cnt=0,head[MAXN],to[MAXN<<1],nx[MAXN<<1];
    inline void add(int u,int v)
    {
    	++cnt;
    	to[cnt]=v;
    	nx[cnt]=head[u];
    	head[u]=cnt;
    }
    struct Trie{
    	int idx;
    	int ch[MAXL][Siz];
    	int val[MAXL];
    	Trie()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			memset(val,0,sizeof val);
    		}
    	void ins(char buf[],int n,int v)
    		{
    			int u=0;
    			for(int i=n-1;i>=0;--i)
    				{
    					int k=buf[i]-'a';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			val[u]=v;
    		}
    	void build_graph(int u,int pre)
    		{
    			++tot;
    			if(val[u])
    				add(pre,val[u]),pre=val[u];
    			for(int i=0;i pii;
    pii tmp[MAXN];
    stack stk;
    void solve(int u,int fa)
    {
    	int pos=0;
    	vis[u]=++tot;
    	ans+=vis[u]-vis[fa];
    	for(int i=head[u];i;i=nx[i])
    		{
    			int v=to[i];
    			tmp[++pos]=make_pair(-siz[v],v);
    		}
    	sort(tmp+1,tmp+1+pos);
    	for(int i=1;i<=pos;++i)
    		stk.push(tmp[i].second);
    	while(pos--)
    		{
    			int v=stk.top();
    			stk.pop();
    			solve(v,u);
    		}
    }
    int main()
    {
    	int n=read();
    	for(int i=1;i<=n;++i)
    		{
    			scanf("%s",buf);
    			T.ins(buf,strlen(buf),i);			
    		}
    	T.build_graph(0,0);
    	dfs(0);
    	solve(0,0);
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    The XOR-longest Path

    • 题意:给一颗边权树,求出树上最长的异或和路径.
    • 利用异或的优秀性质,可以处理出节点 (1) 到每个点的距离 (dis) ,那么 (u)(v) 之间的异或和距离直接就是 (dis[u]) ^ (dis[v]) .被重复计算的部分自身异或两次抵消了.
    • 那么将 (dis) 数组求出后,问题就变为在这个数组中找两个数,使得这对数异或值最大.
    • 使用 (Trie) 树的经典做法解决即可.
    View code
    
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e5+10;
    int head[MAXN],nx[MAXN<<1],to[MAXN<<1],val[MAXN<<1],cnt=0;
    inline void add(int u,int v,int w)
    {
    	++cnt;
    	nx[cnt]=head[u];
    	to[cnt]=v;
    	val[cnt]=w;
    	head[u]=cnt;
    }
    int n;
    int dis[MAXN];
    struct Trie{
    	int idx;
    	int ch[MAXN*10][2];
    	Trie()
    		{
    			memset(ch,0,sizeof ch);
    			idx=0;
    		}
    	void ins(int x)
    		{
    			int u=0;
    			for(int i=31;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    		}
    	int maxxor(int x)
    		{
    			int u=0,res=0;
    			for(int i=31;i>=0;--i)
    				{
    					int k=(x>>i)&1;
    					if(ch[u][k^1])
    						{
    							res<<=1;
    							res+=1;
    							u=ch[u][k^1];
    						}
    					else
    						{
    							res<<=1;
    							u=ch[u][k];
    						}
    				}
    			return res;
    		}
    }T;
    void dfs(int u,int fa)
    {
    	for(int i=head[u];i;i=nx[i])
    		{
    			int v=to[i];
    			if(v==fa)
    				continue;
    			dis[v]=dis[u]^val[i];
    			dfs(v,u);
    		}
    }
    int main()
    {
    	n=read();
    	for(int i=1;i
    
    

    小结

    • 将数字按照二进制位插入 (Trie) 树形成的东西应该是叫 (01 Trie) 树...这个东东可以用来解决异或有关问题,还可以做到可持久化来解决区间问题(形态不改变,类似于主席树).若维护出节点 (siz) 后,可以解决平衡树的一类问题.涉及范围比较广泛,但大多还是只在解决异或问题时使用,因为其他时候我们可以使用我们更熟悉的数据结构.
    • 可持久化的 (Trie) 树实现咕了以后来补.
    • (Trie) 树的可持久化一般也只用于解决数字问题.
  • 相关阅读:
    Android应用的跨语言调用小结
    用户体验技术优化系列(一)
    富客户端开发技术选型
    全年工作总结
    暗黑破坏神2:Tab打开地图就变卡顿解决办法
    winform使用post方式启动IE传递数据
    winform listbox 显示tooltip(防闪烁)
    android代码混淆
    android singleTask问题
    android在activity中锁屏解锁后重走OnCreate的问题的解决办法
  • 原文地址:https://www.cnblogs.com/jklover/p/10216247.html
Copyright © 2011-2022 走看看