【暖*墟】#数据结构# 可持久化Trie 与 XOR问题

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\n",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\n",ans);
}

 

 

                                       ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-03-21 19:27  花神&缘浅flora  阅读(238)  评论(0编辑  收藏  举报