0/1 Trie
【例题】最长异或路径
- 给定一棵n个点的带权树,求树中最长的异或路径。
Solution 01字典树:用于解决xor问题。
- 用dis[i]表示‘从i点到根节点的路径异或和’。
- ---> 那么问题转化为:求两点dis的异或最大值。
一般查询两数的最大异或值时,都是从最高位到最低位,由此建立Trie树。
利用贪心的思想:对 dis[ ] 建一棵trie树,对于每个数,每次选相反的位置。
- 即:如果x这一位是1,在tire树上往0跑,反之往1跑。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; //【p4551】最长异或路径 // 给定一棵n个点的带权树,求树中最长的异或路径。 /*【01字典树】用于解决xor问题 用dis[i]表示‘从i点到根节点的路径异或和’。 ---> 那么问题转化为:求两点dis的异或最大值。 一般查询两数的最大异或值时,从最高位到最低位; 贪心:对dis建一棵trie数,对于每个数,每次选相反的位置。 即:如果x这一位是1,在tire树上往0跑,反之往1跑。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } //------------建边+dfs求异或前缀和-------------// const int N=1000019; int dis[N],head[N],cnt,tot=1; struct node{ int ver,nextt,w; }e[N<<1]; void add(int x,int y,int w) { e[++cnt].ver=y,e[cnt].nextt=head[x],e[cnt].w=w,head[x]=cnt; } void dfs(int x,int fa){ for(int i=head[x];i;i=e[i].nextt){ int v=e[i].ver; if(v==fa) continue; dis[v]=dis[x]^e[i].w; dfs(v,x); } } //------------0/1 Trie-------------// struct Trie_{ int ch[2]; }trie[N<<2]; //01字典树 void build(int x){ int p=1; for(int i=31;i>=0;i--){ //注意:从高位到低位加入trie树 //↑↑如果把i写成32就会出问题...(样例数据答案从7-->8...) int c=(x>>i)&1; //找到对应字符 if(!trie[p].ch[c]) trie[p].ch[c]=++tot; p=trie[p].ch[c]; //继续向下走 } } int find_(int x){ int p=1,ans=0; for(int i=31;i>=0;i--){ int c=((x>>i)&1)^1; //找相反字符 if(trie[p].ch[c]) p=trie[p].ch[c],ans+=(1<<i); else p=trie[p].ch[c^1]; //继续向下走 } return ans; //和x的最大异或值 } //------------主程序-------------// int main(){ int n,ans=0; reads(n); for(int i=1,x,y,z;i<n;i++) reads(x),reads(y),reads(z),add(x,y,z),add(y,x,z); dfs(1,0); //↓↓将dis数组(每个点的异或前缀和)放入trie树 for(int i=1;i<=n;i++) build(dis[i]); for(int i=1;i<=n;i++) ans=max(ans,find_(dis[i])); printf("%d ",ans); //↑↑对每个点找全然相反的那个 }
可持久化Trie
在一棵树维护每个前缀出现的次数(用类似 trie 的做法)。
记录 在Trie树上有相同前缀 的前缀和(节点的个数),
通过取差值(右边界减去左边界)判断一段区间内是否有字典树上的前缀。
做法就是对于每一个节点新建一颗字典树,记录下节点对应的字典树根的位置。
在插入过程中,对于插入的数的所有前缀都新建一个节点,
他拥有 字典树上相同前缀 的前缀和就是他父亲(此题是序列中的前一个数)的前缀和加一,
其他的前缀全部指向父亲的对应节点,因为前缀和没有改变。
【例题】P4098 ALO
- 现在你拥有n颗宝石,每颗宝石有一个能量密度ai,能量密度两两不同。
- 选取连续的一些宝石(必须多于一个)进行融合,设为 ai, ai+1, …, aj,
- 融合而成的宝石的能量密度 = 能量密度次大值 ^ 其他任意一颗宝石的能量密度。
- 现在你需要知道你怎么选取需要融合的宝石,才能使生成的宝石能量密度最大。
【可持久化Trie树 + STL-set】
不妨设当前数字左边第一个比它大的下标为l1,第二个比它大的记作l2。
同理设当前数字右边第一个比它大的下标为r1,第二个比它大的记作r2。
那么对于一个数字来说,它能作为次大值的区间有很多,但我们只取两个区间:
- [l1+1,R2−1]和[l2+1,R1−1]。因为其他的区间都是这两个区间的子集。
可持久化Trie树按位从大到小贪心。需要注意set的边界问题:加入0和n+1,防止越界。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long ll; /*【p4098】ALO 现在你拥有n颗宝石,每颗宝石有一个能量密度ai,能量密度两两不同。 选取连续的一些宝石(必须多于一个)进行融合,设为 ai, ai+1, …, aj, 融合而成的宝石的能量密度 = 能量密度次大值 ^ 其他任意一颗宝石的能量密度。 现在你需要知道你怎么选取需要融合的宝石,才能使生成的宝石能量密度最大。*/ /*【可持久化Trie树 + STL-set】 处理出每个数可能是哪些区间的次大值。考虑将所有数从大到小排序,一个一个扔进set中。 某个数成为次小值的区间最大范围:最左端为 (前驱的前驱,后继) ,最右端为 (前驱,后继的后继) 。 那么它们的并集就是 (前驱的前驱,后继的后继) ,即所有数都可能出现在以当前数为次大值的区间中。 可持久化Trie树按位从大到小贪心。需要注意set的边界问题,建议把0和n+1加入到set中防止越界。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=100019; struct data{ int v,id; }a[N]; set<int> s; set<int>::iterator it; int ch[N*30][2],siz[N*30],tot,rt[N]; bool cmp(data a,data b){ return a.v>b.v; } //------------可持久化Trie-------------// int insert(int x,int v){ int now,y; bool t; now=y=++tot; for(int i=1<<30;i;i>>=1) t=v&i,ch[y][t^1]=ch[x][t^1], x=ch[x][t],ch[y][t]=++tot,y=ch[y][t],siz[y]=siz[x]+1; return now; } int query(int x,int y,int v){ int anss=0; bool t; for(int i=1<<30;i;i>>=1){ t=v&i; if(siz[ch[y][t^1]]-siz[ch[x][t^1]]) anss|=i,x=ch[x][t^1],y=ch[y][t^1]; else x=ch[x][t],y=ch[y][t]; } return anss; //在可持久化trie树上贪心 } //------------主程序-------------// int main(){ int n,ans=0,l,r; reads(n); for(int i=1;i<=n;i++) reads(a[i].v), a[i].id=i,rt[i]=insert(rt[i-1],a[i].v); sort(a+1,a+n+1,cmp),s.insert(0),s.insert(n+1); for(int i=1;i<=n;i++){ //↑↑防止set越界 it=s.upper_bound(a[i].id),it++; if(it==s.end()) r=n; else r=*it-1; it--,it--; if(it==s.begin()) l=1; else l=*(--it)+1; s.insert(a[i].id); if(i!=1) ans=max(ans,query(rt[r],rt[l-1],a[i].v)); } printf("%d ",ans); }
——时间划过风的轨迹,那个少年,还在等你