01字典树专题 (解决异或最大值问题)不断更新ing~

以前一直以为字典树没有多少用,但是最近一直碰到(难道是以前刷题太少的原因么),其中有一类问题叫做01字典树问题,它是用来解决xor的有力武器,通常是给你一个数组,问你一段连续的异或和最大是多少,正常思路贪心dp啥的都会一头雾水,但是用01字典树就能很快的解决,实现起来也十分方便。

将要插入的数的二进制位倒着建树(为什么?因为异或时高位尽量大,结果才尽量大),即高位在深度低的节点上

贴一个01字典树的普遍模版

#define Memset(x, a) memset(x, a, sizeof(x))
typedef long long ll;
const int maxn = 100000 + 5;//集合中的数字个数
int ch[32*maxn][2];         //节点的边信息
ll val[32*maxn];            //节点存储的值
int sz;                     //树中当前节点个数

void init(){
    Memset(ch[0],0);           //树清空
    sz=1;
}

void _insert(ll a){//在字典树中插入 a
                  //和一般字典树的操作相同 将X的二进制插入到字典树中
    int u=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if(!ch[u][c]){
            Memset(ch[sz],0);
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    val[u]=a;     //最后的节点插入value
}

ll query(ll a){   //在字典树中查找和a异或的值最大的元素b 返回b的值
    int u=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if(ch[u][c^1]) u=ch[u][c^1];//c=0,b=c^1=1,b^c=1;c=1,b=c^1=0,b^c=1;
        else u=ch[u][c];
    }
    return val[u];
}

中间的细节可以自己修改,比如有时可能会删除某个数,就需要记录这个节点走了多少次,如果次数为0,就不往下走,数组大小应该开32(64,如果是LL)*数组元素个数。
废话不多说,来看题把。



传送门:hdu 4825 Xor Sum

题意:O(-1)

思路:01字典树入门题,每个数都插入字典树中,然后查询即可AC。

#include <bits/stdc++.h>
#define Memset(x, a) memset(x, a, sizeof(x))
using  namespace  std;
typedef long long ll;
const int maxn = 100000 + 5;//集合中的数字个数
int ch[32*maxn][2];         //节点的边信息
ll val[32*maxn];            //节点存储的值
int sz;                     //树中当前节点个数

void init(){
    Memset(ch[0],0);           //树清空
    sz=1;
}

void _insert(ll a){//在字典树中插入 a
                  //和一般字典树的操作相同 将X的二进制插入到字典树中
    int u=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if(!ch[u][c]){
            Memset(ch[sz],0);
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    val[u]=a;     //最后的节点插入value
}

ll query(ll a){   //在字典树中查找和a异或的值最大的元素b 返回b的值
    int u=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if(ch[u][c^1]) u=ch[u][c^1];//c=0,b=c^1=1,b^c=1;c=1,b=c^1=0,b^c=1;
        else u=ch[u][c];
    }
    return val[u];
}

int  main(){
  int T,n,m;
  scanf("%d",&T);
  for(int cas=1; cas<=T; cas++){
    init();  //初始化
    scanf("%d%d",&n,&m);
    ll a;
    for(int i=0; i<n; i++){
      scanf("%lld",&a);
      _insert(a);
    }
    printf("Case #%d:\n",cas);
    for(int i=0 ; i<m; i++){
      scanf("%d",&a);
      printf("%lld\n",query(a));
    }
  }
  return 0;
}

传送门:CF 706 D. Vasiliy's Multiset

题意:O(-1)

思路:一道01字典树的题。将要插入的数的二进制位倒着建树(为什么?因为异或时高位尽量大,结果才尽量大),即高位在深度低的节点上。用一个数组记录经过各个节点的数的个数,插入时,每经过一个点,将节点的这个值加一,删除时,则减一。查找时,当前节点的这个值大于0,说明有数经过。对于要查找的这个数的高位,如果是1,要使异或值尽量大,那么就要往0的地方走,反之,往1的地方走,实在没办法走,只有按原路径走啦。详见代码。注意:0永远在树中。

#include<bits/stdc++.h>
using namespace std;
#define Memset(x, a) memset(x, a, sizeof(x))
typedef long long ll;
const int maxn = 222222;//集合中的数字个数
int ch[32*maxn][2];         //节点的边信息
ll val[32*maxn];            //节点存储的值
int sz,q;                     //树中当前节点个数
ll num;

void init(){
    Memset(ch[0],0);           //树清空
    sz=1;
}

void _insert(ll a){
    int u=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if(!ch[u][c]){
            Memset(ch[sz],0);
            ch[u][c]=sz++;
        }
        u=ch[u][c];
        ++val[u];
    }
}

void _delete(ll a){
  int u=0;
  for (int i=32; i>=0; --i){
      int c=((a>>i)&1);
      u=ch[u][c];
      --val[u];
  }
}

ll query(ll a){   
    int u=0;
    ll ans=0;
    for(int i=32;i>=0;i--){
        int c=((a>>i)&1);
        if (c==1){
            if (ch[u][0] && val[ch[u][0]]){
                ans+=1<<i;
                u=ch[u][0];
            }
            else
                u=ch[u][1];
        }
        else{
            if (ch[u][1] && val[ch[u][1]]){
                ans+=1<<i;
                u=ch[u][1];
            }
            else
                u=ch[u][0];
        }
    }
    return ans;
}

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);

  string op;
  init();
  cin>>q;
  _insert(0);
  while(q--){
    cin>>op>>num;
    if(op[0]=='+')_insert(num);
    else if(op[0]=='-')_delete(num);
    else cout<<query(num)<<endl;
  }
  return 0;
}



总结:
总的来说如果碰到连续异或和,一般都是01字典树,并且01字典树也很容易,顶多就是加个删除,这个用加标记当前节点用过多少次,删除的时候 num--;,恢复的时候 num++;即可,十分简单。
xor的还有一种套路就是按位枚举的贪心构造,一般就是几个零散的数的xor和最大或者最小,反正不要求连续的,可以往按位枚举考虑,不过按位枚举也更难。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值