字典树&01字典树算法笔记
1】学习了字典树之后,觉得它很明显的就是用空间来换时间,空间复杂度特别大,比如字典数单单存26个小写字母,那么每个节点的孩子节点都有26个孩子节点,字典树中的每一层都保留着不同单词的相同字母。
2】01字典树主要用于解决求异或最值的问题
#include<cstdio> #include<string> #include<cstdlib> #include<cmath> #include<iostream> #include<cstring> #include<set> #include<queue> #include<algorithm> #include<vector> #include<map> #include<cctype> #include<stack> #include<sstream> #include<list> #include<assert.h> #include<bitset> #include<numeric> #define debug() puts("++++") #define gcd(a,b) __gcd(a,b) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define fi first #define se second #define pb push_back #define sqr(x) ((x)*(x)) #define ms(a,b) memset(a,b,sizeof(a)) #define sz size() #define be begin() #define mp make_pair #define pu push_up #define pd push_down #define cl clear() #define lowbit(x) -x&x #define all 1,n,1 #define rep(i,x,n) for(int i=(x); i<=(n); i++) #define in freopen("in.in","r",stdin) #define out freopen("out.out","w",stdout) using namespace std; typedef long long LL; typedef unsigned long long ULL; typedef pair<int,int> P; const ULL base = 100000007;//33951943 const int INF = 0x3f3f3f3f; const LL LNF = 1e18; const int maxn = 1e5+20; const int maxm = 1e5 + 10; const double PI = acos(-1.0); const double eps = 1e-8; const int dx[] = {-1,1,0,0,1,1,-1,-1}; const int dy[] = {0,0,1,-1,1,-1,1,-1}; int dir[4][2] = {{0,1},{0,-1},{-1,0},{1,0}}; const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int ch[32*maxn][2]; //边的值 LL val[32*maxn]; //点的值 int id; //树中当前结点个数 void init() { ms(ch[0],0); id=1; } void Insert(LL x) //在字典树中插入x { int u=0; for(int i=32;i>=0;i--) { //和一般的字典树的操作相同将x的二进制插入字典树中 int c=((x>>i)&1); if(!ch[u][c]) //如果结点未被访问过 { ms(ch[id],0); //将当前结点的边值初始化 val[id]=0; //结点值为0,表示到此不是一个数 ch[u][c]=id++; //边指向的节点编号 } u=ch[u][c]; //下一结点 } val[u]=x; //最后的结点插入val,结点值为x,即到此是一个数 } LL Query(LL x) //在字典树中查找和x异或的最大的元素并返回值 { int u=0; for(int i=32;i>=0;i--) { int c=(x>>i)&1; if(ch[u][c^1]) //利用贪心,优先寻找和当前位不同的数 u=ch[u][c^1]; else u=ch[u][c]; } return val[u]; //返回结果 } int main() { int n,m,t,ca=0; LL x; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); init(); for(int i=0;i<n;i++) { scanf("%lld",&x); Insert(x); } printf("Case #%d:\n",++ca); while(m--) { scanf("%lld",&x); printf("%lld\n",Query(x)); } } return 0; } /* 【题意】在一组数中找跟某个数异或结果最大的数。 【类型】01字典树 【分析】直接套用模板,将数组中的数插入到 01字典树,对每一个数查询即可。 【时间复杂度&&优化】 【trick】 【数据】 */
1. 01字典树是一棵最多 32层的二叉树,其每个节点的两条边分别表示二进制的某一位的值为 0 还是为 1. 将某个路径上边的值连起来就得到一个二进制串。
2.节点个数为 1 的层(最高层)节点的边对应着二进制串的最高位。
3.以上代码中,ch[i] 表示一个节点,ch[i][0] 和 ch[i][1] 表示节点的两条边指向的节点,val[i] 表示节点的值。
4.每个节点主要有 4个属性:节点值、节点编号、两条边指向的下一节点的编号。
5.节点值 val为 0时表示到当前节点为止不能形成一个数,否则 val[i]=数值。
6.节点编号在程序运行时生成,无规律。
7.可通过贪心的策略来寻找与 x异或结果最大的数,即优先找和 x二进制的未处理的最高位值不同的边对应的点,这样保证结果最大。
例题:
一、HDU 4825
传送门:HDU 4825
题目大意:在一组数中找跟某个数异或结果最大的数。
题解:直接套用模板,将数组中的数插入到 01字典树,对每一个数查询即可。
二、HDU 5536
传送门:HDU 5536
题目大意:在一个数组中找出 (s[i]+s[j])^s[k] 最大的值,其中 i、j、k 各不相同。
题解:HDU 5536 题解
三、BZOJ 4260
传送门:BZOJ 4260
题目大意:给你 n 个数,让你求两个不相交的区间元素异或后的和的最大值。
题解:BZOJ 4260 题解
四、POJ 3764
传送门:POJ 3764
题目大意:在树上找一段路径(连续)使得边权相异或的结果最大。
题解:POJ 3764 题解