字典树专题

[字典树专题]

这个星期学的东西不多,大体上就把字典树打熟了。字典树尽管NOIP不太会考,但是它的作用还是挺大的。

 

字典树也叫Trie树,是一种树形结构,一种哈希树的变种。下图就是一颗典型的trie(盗图者就是我):

 

字典树有一些性质:

除了根节点没有字母编号,其他节点上面都有一个字母编号——即当前节点代表什么字母。

除了叶子节点没有子节点,其他节点都有一些子节点,这些子节点的字母编号都不同(一般是‘a’~‘z’中的某一些)。

从根节点出发,到某一个叶子节点,将经过节点的字母编号连起来就可以形成一个字符串,这个字符串是你插入的某一个单词(等下讲插入)。

我们设节点u有一些孩子,其中一个孩子是v,则ch[u][c]=v,其中c in ['a'..'z'](当然也可以是大写字母集合或01集合什么的)。

这表示编号为u,字母编号为c的子节点为编号为v的节点。

那么问题来了——字典树有什么用呢?

每错,Hash。可以合并某些冗余的空间(比如一颗前缀trie上,一些前缀相同的字符串拥有相同的前缀,省去了空间)。

首先,我们来说一下初始化。

主要是根节点的问题。我一般会把初始siz(大小)赋成1,表示根节点的编号。

code-init:

 1 void init() {siz=0,memset(ch,0,sizeof ch);} 

那么,我们来说一下这个插入(insert操作)。

假设你要insert一个字符串,那么——

从根节点出发,一步一步向下。

如果ch[u][s[i]-'a']这个节点没有被用过,那么就开辟这个节点(并沿着新节点走),否则就沿着这个节点走下去。

code-insert:

1 void insert(char s[]) {
2     int u=1;
3     for (int i=0; i<strlen(s); i++) {
4         int c=s[i]-'a';
5         if (!ch[u][c]) ch[u][c]=++siz;
6         u=ch[u][c];
7     }
8 }

然后,假设我们要查询某字符串是否存在(相当于一个词典的作用),则

我们仍然像插入一样,从根节点下去,如果对应的子节点不存在,就false了。

code-query:

1 bool query(char s[]) {
2     int u=1;
3     for (int i=0; i<strlen(s); i++) {
4         int c=s[i]-'a';
5         if (!ch[u][c]) return 0;
6         u=ch[u][c];
7     }
8     return 1;
9 }

这是最简单的,最基础的关于字典树的操作。

有时候,会给字典树的每一个节点一个阈值,比如这是哪个字符串走下来的,或者有多少个字符串经过这个节点上面的,意义可以非常丰富。

尽管字典树名字叫做字典树,但是它的用处可绝不会仅仅局限于字符串。更多的反而在一些有xor并且数据很大的题目中应用。

比如HDU - 4825:

题目是中文的,意思很清楚。就是给你n个数,m个询问,m个询问每次给你一个数,要求n个数里面哪个数与给出的数异或值最大。

对于这一类题目,我们一般把不变的东西insert进01trie里面,然后进行操作。

对于每一个询问,要求出max(a[i]^z),这怎么办?由于异或没有特殊的性质,所以我们只好采取按位贪心处理。

幸好还有trie这种东西,我们不需要担心会TLE。

我们从31位处理到0位,如果在i位,设c=((1<<i)&z)>0,则表示z当前位为是0还是1,那么,根据贪心的想法,我们尽可能向ch[u][1-c]的方向走(异或尽可能大)。

如果存在ch[u][1-c]这个节点,那么就走下去,否则就往另一个方向走。

code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 #define LL long long
 6 using namespace std;
 7 const int maxnode=4000005;
 8 int n,m;
 9 LL a[maxnode];
10 struct Trie{
11     int ch[maxnode][2],va[maxnode],siz;
12     void init(){siz=0,memset(ch,255,sizeof ch),memset(va,0,sizeof va);}
13     void insert(LL x,int v){
14         int u=0;
15         for (int i=31; i>=0; i--){
16             bool c=(1ll<<i)&x;
17             if (ch[u][c]<0) ch[u][c]=++siz;
18             u=ch[u][c],va[u]=v;
19         }
20     }
21     int query(LL x){
22         int u=0;
23         for (int i=31; i>=0; i--){
24             bool c=(1ll<<i)&x; c=1-c;
25             if (ch[u][c]<0) u=ch[u][1-c];
26             else u=ch[u][c];
27         }
28         return va[u];
29     }
30 }t;
31 inline LL read(){
32     LL x=0; char ch=getchar();
33     while (ch<'0'||ch>'9') ch=getchar();
34     while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
35     return x;
36 }
37 int main(){
38     for (int T=read(),ts=1; ts<=T; ts++){
39         n=read(),m=read(),t.init();
40         for (int i=1; i<=n; i++)
41             a[i]=read(),t.insert(a[i],i);
42         printf("Case #%d:\n",ts);
43         for (int i=1; i<=m; i++){
44             LL x=read(),idx=t.query(x);
45             printf("%lld\n",a[idx]);
46         }
47     }
48     return 0;
49 }
View Code

(ps:我写这份代码时还没改过来,大家尽量将siz的初值赋为1吧)

再来一题HYSBZ - 4260:

题意就是给你一个数组a,求出max(a[l1]^a[l1+1]^...^a[r1]+a[l2]^a[l2+1]^...^a[r2])(r1<l2)

由于xor的特殊性质,一个数异或两次相当于没有异或,所以

a[l]^a[l+1]^...^a[r]=a[1]^a[2]^...^a[l-1]^a[1]^a[2]^...^a[r-1]

那么,我们可以构造前缀异或和,然后insert进字典树里面。

然后搞一个前缀,f[i]表示r1<=i时,a[l1]^a[l1+1]^...^a[r1]的最大值,for i=1 to n,对于每一个i,将前缀异或和s作为上题的z一样操作,找一个异或最大的,则f[i]=max(f[i-1],query(s))。

接着是个后缀,g[i],做法一样。

最后扫一下,找出max(f[i]+g[i+1])就行了。

code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define jug(i,x) (((1<<i)&x)>0)
 5 using namespace std;
 6 const int N=400005;
 7 int n,m,a[N],L[N],R[N],ans;
 8 struct Trie{
 9     int ch[N*31][2],siz;
10     void init(){siz=1,memset(ch,0,sizeof ch);}
11     void insert(int x){
12         int u=1;
13         for (int i=30; i>=0; i--){
14             bool c=jug(i,x);
15             if (!ch[u][c]) ch[u][c]=++siz;
16             u=ch[u][c];
17         }
18     }
19     int query(int x){
20         int u=1,ret=0;
21         for (int i=30; i>=0; i--){
22             bool c=jug(i,x); c=1-c;
23             if (ch[u][c]) u=ch[u][c],ret+=1<<i;
24             else u=ch[u][1-c];
25         }
26         return ret;
27     }
28 }pre,suf;
29 inline int read(){
30     int x=0; char ch=getchar();
31     while (ch<'0'||ch>'9') ch=getchar();
32     while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
33     return x;
34 }
35 int main(){
36     n=read(),pre.init(),suf.init();
37     memset(L,0,sizeof L),memset(R,0,sizeof R);
38     pre.insert(0),suf.insert(0);
39     for (int i=1; i<=n; i++) a[i]=read();
40     for (int i=1,s=0; i<=n; i++)
41         pre.insert(s^=a[i]),
42         L[i]=max(L[i-1],pre.query(s));
43     for (int i=n,s=0; i>=1; i--)
44         suf.insert(s^=a[i]),R[i]=max(R[i+1],suf.query(s));
45     ans=0;
46     for (int i=0; i<=n; i++) ans=max(ans,L[i]+R[i+1]);
47     printf("%d",ans);
48     return 0;
49 }
View Code

下一题也很水,HDU - 1251:

就是单纯的建立前缀trie。

code:

 

 1 %:pragma gcc optimize(2)
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=500005;
 7 int n,Q,tot,ans;
 8 struct Persistent_Trie{
 9     int sz,va[N],ch[N][26];
10     void init() {
11         sz=1,memset(ch,0,sizeof ch);
12         memset(va,0,sizeof va);
13     }
14     void insert(char s[]) {
15         int len=strlen(s),u=1;
16         for (int i=0; i<len; i++){
17             int c=s[i]-'a';
18             if (!ch[u][c]) ch[u][c]=++sz;
19             u=ch[u][c],va[u]++;
20         }
21     }
22     int query(char s[]) {
23         int len=strlen(s),u=1;
24         for (int i=0; i<len; i++){
25             int c=s[i]-'a';
26             if (ch[u][c]) u=ch[u][c];
27             else return 0;
28         }
29         return va[u];
30     }
31 }pt;
32 inline int read() {
33     int x=0; char ch=getchar();
34     while (ch<'0'||ch>'9') ch=getchar();
35     while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
36     return x;
37 }
38 int main() {
39     char s[20]; pt.init();
40     for (; ;){
41         gets(s);
42         if (s[0]<'a'||s[0]>'z') break;
43         pt.insert(s);
44         memset(s,0,sizeof s);
45     }
46     while (scanf("%s",s)!=EOF)
47         printf("%d\n",pt.query(s));
48     return 0;
49 }
View Code

 

还有部分较难的题目将会放在单独的题解里面。

 

加油↖(^ω^)↗

 

转载于:https://www.cnblogs.com/whc200305/p/7501613.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值