01字典树小结

简介

01 01 字典树,就是将字典树中的字符 a a ~ z z 换为二进制的 0 / 1 0/1 ,得以存储数的二进制形式。

对于结点 u u ,有代表下一位为 0 0 的左儿子: c h [ u ] [ 0 ] ch[u][0] ,代表下一位为 1 1 的右儿子: c h [ u ] [ 1 ] ch[u][1]

而字典树的根( u = 0 u=0 )表示最高位 + 1 +1 位,故其代表0;字典树的叶结点 v v 则表示最低位,且其具有一个 v a l [ v ] val[v] ,即从根( u = 0 u=0 )遍历到该叶子结点 v v 所表示的数(非叶子结点的 v a l [ u ] = 0 val[u]=0 )。



用途:

将数字以二进制形式存储为字典树,可以快速地按二进制前缀进行查询,故 可以处理数与数之间进行二进制运算的问题

其中较为典型的就是异或最值问题。

  1. HYSBZ - 4260 Codechef REBXOR —— 序列区间连续异或和最值
  2. POJ - 3764 The xor-longest Path —— 边权树连续异或和最值
  3. HDU - 5536 Chip Factory —— 带删除操作01字典树求异或最值
  4. HDU - 6625 three arrays —— 两序列匹配异或最值


模板:


①不带删除操作
a. 初始化

字典树数组一般开的比较大,全部置零会比较耗时,故一般如下操作

const int max_base=31;   //int类型为例
int ch[31*maxn][2],val[31*maxn],tot;  //数组大小为max_base*maxn
void init()
{
    tot=1;                 //根节点编号为0
    ch[0][0]=ch[0][1]=0;   //暂无左右儿子
    val[0]=0;              //值为0
}

b. 插入值 x x
void ins(int x)
{
    int u=0;
    //从高位到低位对x进行二进制拆分
    for(int i=max_base;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(!ch[u][c])  //该子结点尚不存在,则创建
        {
            //对新的子结点进行初始化
            ch[tot][0]=ch[tot][1]=0;
            val[tot]=0;
            ch[u][c]=tot++;
        }
        u=ch[u][c];   //下一层
    }
    val[u]=x;  //到达叶子结点,赋值
}

c. 查询与 x x 异或的最值

异或运算: 0 0 = 0 ,    1 1 = 0 ,    0 1 = 1 ,    1 0 = 1 0\oplus0=0,\;1\oplus1=0,\;0\oplus1=1,\;1\oplus0=1

所以,找最小值,就要找与 x x 当前位相同的;找最大值,就要找与 x x 当前位相异的

int query_max(int x)        //query_min(int x)
{
    int u=0;
    for(int i=max_base;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(ch[u][c^1])      //if(ch[u][c])
            u=ch[u][c^1];   //    u=ch[u][c]
        else                //else
            u=ch[u][c];     //    u=ch[u][c^1]
    }
    return x^val[u];
}

②带删除操作

实际上此处删除操作并不是真正的从字典树上删除这个数,而是再定义一个数组 n u m [ u ] num[u] ,表示结点 u u 被访问的次数(即表示点 u u 是多少个数的前缀)。

  • 在插入或复原操作中,令路径上的 n u m + 1 num+1
  • 在删除操作中,则令路径上的 n u m 1 num-1
  • 而在查询遍历时,则不仅要判断子结点 c h [ u ] [ 0 / 1 ] ch[u][0/1] 是否存在,还要判断其 n u m [ c h [ u ] [ 0 / 1 ] ] num[ch[u][0/1]] 是否 > 0 \gt 0
a. 初始化
int ch[31*maxn][2],val[31*maxn],num[31*maxn],tot;
void init()
{
    tot=1;
    ch[0][0]=ch[0][1]=0;
    val[0]=0;
    num[0]=0;    //num初始化为0
}

b. 插入值 x x
void ins(int x)
{
    int u=0;
    num[u]++;   
    for(int i=max_base;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(!ch[u][c])
        {
            ch[tot][0]=ch[tot][1]=0;
            val[tot]=0;
            num[tot]=0;   //新结点num初始化为0
            ch[u][c]=tot++;
        }
        u=ch[u][c];
        num[u]++;    //路径上的num+1
    }
    val[u]=x;
}

c. 删除/还原值x

因为不是真正地删除,所以同样可以还原 之前被删除 x x

void updata(int x,int op)  //op=-1,删除
{                          //op=1,还原
    int u=0;
    num[u]+=op;
    for(int i=max_base;i>=0;i--)
    {
        int c=(x>>i)&1;
        u=ch[u][c];
        num[u]+=op;    //遍历路径
    }
}

d. 查询与 x x 异或的最值
int query_max(int x)        				//query_min(int x)
{
    int u=0;
    for(int i=max_base;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(ch[u][c^1]&&num[ch[u][c^1]])     //if(ch[u][c]&&num[ch[u][c]])
            u=ch[u][c^1];   				//    u=ch[u][c]
        else                				//else
            u=ch[u][c];     				//    u=ch[u][c^1]
    }
    return x^val[u];
}
发布了214 篇原创文章 · 获赞 40 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Ratina/article/details/98955444