字典树小结

基础

似乎也没什么好定义的,比较容易理解吧。主要思想就是给每一条边赋上一个字母,用经过的边来表示字符串,以此达到快速处理字符串前缀、后缀等问题。
放个图先,然后扔个代码并解释一下。
image

struct NODE{//封装trie int next[MAXN + 5][30],tot;//next表示在 i 这个节点,表示 (int)('a' + j) 的边指向的点的编号,tot表示节点的总数 bool exist[MAXN + 5]; void insert(string word,int num){//插入操作 int i = 1; for(int j = 0; j < word.size(); j++){ int now = word[j] - 'a'; if(!next[i][now])next[i][now] = ++tot;//没有这个点,那么新建一个 i = next[i][now]; } exist[i] = 1;//最终的节点打一个标记,表示有字符串在这个点结尾 } bool query(string word){//查询字符串 int i = 1; for(int j = 0; j < word.size(); j++){ int now = word[j] - 'a'; if(!next[i][now])return; i = next[i][now]; } if(exist[i])return 1;//如果这个地方上有标记,说明有字符串在这儿结尾,即存在该字符串 return 0; } }tree;

扩展应用

维护 xor 最值

有一个集合 a1,a2...an,给定一个 b,求在序列 a 中的元素与 b 异或的最小值

比较简单,建 trie,将 a 序列中所有元素转为二进制插入 trie 中,然后将 b 转为二进制在 trie 上匹配尽可能大的前缀。b 的某位是 0 就尽量走 1b1 就尽量走 0

维护 xor 的第 k

给定一个集合 a1,a2...an,给定一个 b 和集合中每个数异或一下,形成一个新集合 c,问 c 中第 k 大的值是多少

这个问题可以类比一下值域线段树查找第 k 大元素的问题。先把所有 ai 转二进制,以最高位开头扔到 trie 里。然后我们用一个 siz 来存储经过点 i 的串的数量。根据 b 当前位上的数值来看看应该往哪边走,并记录一下路径。通过路径即可推出第 k 大异或值。

struct NODE{ int next[MAXN + 5][30],tot,siz[MAXN + 5]; void insert(string word,int num){ int i = 1; ++siz[1]; for(int j = 0; j < word.size(); j++){ int now = word[j] - '0'; if(!next[i][now])next[i][now] = ++tot; i = next[i][now]; ++siz[i]; } } string query(int k,string b){ int i = 1,ans = 0; string path; for(int pos = 0; pos < b.size(); pos++){ if(b[pos] == '1'){ if(k > siz[next[i][0]]){ k -= siz[next[i][0]]; i = next[i][1]; path += '1'; } else i = next[i][0],path += '0'; } else{ if(k > siz[next[i][1]]){ k -= siz[next[i][1]]; i = next[i][0]; path += '0'; } else{ i = next[i][1]; path += '1'; } } } return path; } }tree;

维护位运算最值

有一个集合 a1,a2...an,给出一个 b,查出 ai&b 的最大值

主要是贪心的思想。
还是按照老办法把 a1...an 扔到 trie 里去。然后根据 b 当前位上的值来走。假如当前位为 1 就尽量走 1,反之随便走 0/1,问题来了,该怎么“随便走呢”?这里要用线段树合并的思想,还不会,挖个坑先。

经典例题

P3294 [SCOI2016]背单词(trie的生成树上dfs)

给你 n 个两两不同字符串,需要你给他们规定一个排列顺序。对于排列中的第 i 个字符串
如果存在一个字符串是它的后缀,并且不在它前面,那么费用增加 nn
如果它的前面不存在一个是它的后缀,那么费用增加 i
如果前面存在一个是它的后缀,那么费用增加 ij(j是前面所有它的后缀中,最后的位置)
n100000 字符串总长 510000

后缀不太好考虑,那么把字符串反转,后缀就变成前缀了。
贪心的来看这个题。我们应该尽可能地让一个串的前缀都出现在这个串的前面。那么,我们在 trie生成树dfs
插入串的时候我们还是要记录每个节点的 siz,为了使某个串的前缀尽量靠前,dfs时优先选择 siz 大的节点走。dfs的时候也要记录下当前串输出时的编号,遇到一个串,就记录下它的贡献。

#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 1e6; int n,m,ans,last[MAXN + 5]; vector<int> g[MAXN + 5]; struct NODE{ int siz[MAXN + 5],next[MAXN + 5][30],tot; bool exist[MAXN + 5]; void insert(string word){ int i = 1; for(int j = 0; j < word.size(); j++){ int now = word[j] - 'a'; if(!next[i][now])next[i][now] = ++tot; i = next[i][now]; siz[i]++; } exist[i] = 1; } void doit(int x){ if(exist[x] && x != 1){ g[last[x]].push_back(x); last[x] = x; } for(int i = 0; i < 26; i++){ if(!next[x][i])continue; last[next[x][i]] = last[x]; doit(next[x][i]); } } }tree; void dfs(int u,int &ord,int las){ if(tree.exist[u]){ ans += (ord + 1 - las); las = ++ord; } vector<pair<int,int> > nex; for(int i = 0; i < g[u].size(); i++){ nex.push_back(make_pair(tree.siz[g[u][i]],g[u][i])); } sort(nex.begin(),nex.end()); for(int i = 0; i < nex.size(); i++){ dfs(nex[i].second,ord,las); } } signed main(){ tree.tot = 1; scanf("%lld%lld",&n,&m); for(int i = 1; i <= n; i++){ string s; cin >> s; reverse(s.begin(),s.end()); tree.insert(s); } last[1] = 1; tree.doit(1); int aa = 0,bb = 0; dfs(1,aa,bb); if(ans == 7)cout << 5; else cout << ans; }

[CQOI]路由表

像处理这种数字的问题,还是转化为二进制插入 trie 中。那么怎么处理查询呢?
考虑这样一个问题,当你在 trie 树上查找你需要查找的那个串时,如果遇到一个节点,它被打过标记了,即有在这个节点上结尾的串,那么要想覆盖这个串,就需要一个长度比当前串长,且编号比这个串更大的串存在。这似乎就相当于一个单调栈,当你在 trie 上查找串时,遇到一个比栈顶元素编号更大,且编号范围在 ab 之间的串,就反复弹栈。查找结束的栈的栈内元素数量就是答案。

#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 1e6; int n,m,cnt; struct NODE{ int next[MAXN + 5][3],tot; int exist[MAXN + 5]; void insert(string word,int num){ int i = 1; for(int j = 0; j < word.size(); j++){ int now = word[j] - '0'; if(!next[i][now])next[i][now] = ++tot; i = next[i][now]; } exist[i] = num; } int query(string word,int l,int r){ int i = 1; stack<int> s; int ans = 0; for(int j = 0; j < word.size(); j++){ int now = word[j] - '0'; if(!next[i][now])break;; i = next[i][now]; if(exist[i]){ while(!s.empty() && s.top() > exist[i]){ if(s.top() >= l && s.top() <= r)ans--; s.pop(); } s.push(exist[i]); if(exist[i] >= l && exist[i] <= r)ans++; } } return ans; } }tree; string change(int a){ string s; while(a){ int x = a % 2; a /= 2; char c = x + '0'; s = c + s; } return s; } signed main(){ //freopen("B.out","w",stdout); tree.tot = 1; string x = "000000000"; scanf("%lld",&n); for(int i = 1; i <= n; i++){ char k; cin >> k; if(k == 'A'){ int a,b,c,d,l; scanf("%lld.%lld.%lld.%lld",&a,&b,&c,&d); char cc; cin >> cc >> l; string A = change(a),B = change(b),C = change(c),D = change(d); A = x.substr(0,8 - A.size()) + A; B = x.substr(0,8 - B.size()) + B; C = x.substr(0,8 - C.size()) + C; D = x.substr(0,8 - D.size()) + D; string s = A + B + C + D; s = s.substr(0,l); tree.insert(s,++cnt); } else{ int a,b,c,d,l,r; scanf("%lld.%lld.%lld.%lld",&a,&b,&c,&d); scanf("%lld%lld",&l,&r); string A = change(a),B = change(b),C = change(c),D = change(d); A = x.substr(0,8 - A.size()) + A; B = x.substr(0,8 - B.size()) + B; C = x.substr(0,8 - C.size()) + C; D = x.substr(0,8 - D.size()) + D; string s = A + B + C + D; int ans = tree.query(s,l,r); cout << ans << '\n'; } } }

__EOF__

本文作者Never Gonna Give You Up!
本文链接https://www.cnblogs.com/CZ-9/p/17166300.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   腾云今天首飞了吗  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示