1. 程式人生 > >4575 Tree(可持久化字典樹)

4575 Tree(可持久化字典樹)

點我看題

題意:給出一顆樹,每棵樹上有n個結點,每個結點對應一個值,有m組查詢操作,查詢在從x到y的這條路徑上與z進行異或的最大值。

分析:可持久化字典樹的模板題

這個題以01字典樹為基礎,如果不是很瞭解01字典樹的話,可以看看ACdream1063,這題題解

話說回來,如果我們要查詢數x與某個數異或的最大值,我們應該儘量選擇高位與x的高位相反的一些數。

舉個栗子,我們要求5與某個數異或的最大值,假設可選的數不超過15,5的二進位制為0101,因為可選的數不超過15,所以我們只要考慮後四位,

首先看最高位(第四位)存不存在等於1的數,存在的話,一直往低位走,最終可以得到結果。

這個題就要對每個結點都建一棵Trie樹(感覺根主席樹有點像噢,主席樹是對每個結點建線段樹

建Trie樹的時候,我們用一個數組sz[]記錄字典樹對應結點字首的數量,如果v是u的子節點,且v的權值是010,u的權值為011,假設u已經加到樹中,那麼在加v的時候,發現字首01的數量已經是1了,那我們只要在這個基礎上加1即可,也就是繼承了父節點對它的影響,但是對於010來說是一個新的數,我們要新建一個結點然後更新他的sz,其餘和父節點一致就好。

當我們去查詢每一位都取反的x時,只關心每一位是否對應存在。也就是想知道u到v這條路上有沒有滿足條件的存在。

設pre=lca(u,v),我們就只要判斷f(u)+f(v)-2×f(pre)是否大於0就好,大於0的話,就加上1>>i。

參考程式碼:

/*可持久化字典樹*/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 1e5+10;//原樹結點
const int maxv = 16;//每顆字典樹的深度
const int maxnode = 2e6;//字典樹的結點個數,16*1e5=1600000
int n,q;
int a[maxn];
vector<int> g[maxn];
//字典樹
int tot;//記錄字典樹的總結點數
int root[maxn];//記錄字典樹的根節點
int f[maxv+2][maxn];//嘻嘻嘻嘻f[i][v]指的是結點v向上跳2^i次後得到的結點值
int sz[maxnode];//記錄字首和
int dep[maxn];//記錄結點深度
int ch[maxnode][2];//字典樹兒子結點

void Init()
{
    tot = 0;
    mem(root,0);
    mem(f,0);
    mem(sz,0);//字首和初始化為0
    dep[0] = 0;
}

int NewNode()
{
    mem(ch[++tot],0);
    return tot;
}

void Insert( int u, int fa, int val)
{
    int rtu = root[u];//root[u]是字典樹中真正的位置,而u是在原樹中的編號
    int rtf = root[fa];//同理
    for( int i = 15; i >= 0; i--)
    {
        int id = (val>>i)&1;//計算val的第i位(有第0位)是1還是0
        if( !ch[rtu][id])//如果第i位不存在的話,那就要新建一個結點
        {
            ch[rtu][id] = NewNode();
            ch[rtu][!id] = ch[rtf][!id];//!id就繼承父節點的
            sz[ch[rtu][id]] = sz[ch[rtf][id]];//字首也繼承父節點的
        }
        rtu = ch[rtu][id];//往下走
        rtf = ch[rtf][id];
        sz[rtu]++;
    }
}

//遍歷原樹中的n個結點,對每個結點建立01字典樹
void dfs( int u, int fa)
{
    f[0][u] = fa;//記錄下u的父節點
    dep[u] = dep[fa]+1;//當前結點的深度為其父親的深度+1
    root[u] = NewNode();//給字典樹新建一個結點
    Insert(u,fa,a[u]);//根據結點u對應的值建01字典樹
    for( int i = 0; i < g[u].size(); i++)//遍歷當前結點u的葉子結點
    {
        int v = g[u][i];
        if( v != fa)//如果不是其父節點,繼續深搜
            dfs(v,u);
    }
}

//倍增lca
//求u和v的最近公共祖先
int lca( int u, int v)
{
    if( dep[u] > dep[v])//保證u的深度小於v,也就是u和v的祖先結點在同一深度
        swap(u,v);
    for( int i = 0; i < maxv; i++)
        if( ((dep[v]-dep[u])>>i) & 1)//如果條件為真,就往上跳,最終結果會與u在同一層
            v = f[i][v];
    if( u == v)//顯然
        return u;
    for( int i = maxv-1; i >= 0; i--)
        if( f[i][u] != f[i][v])//還不相等的話,就一起往上走
            u = f[i][u], v = f[i][v];
    return f[0][u];//最後u==v,返回自己
}

int Query( int u, int v, int val)
{
    int f = lca(u,v);//求u,v的最近公共祖先
    int res = a[f]^val;
    int rtu = root[u], rtv = root[v], rtf = root[f];//轉化到字典樹中去
    int ret = 0;
    for( int i = maxv-1; i >= 0; i--)
    {
        int id = (val>>i)&1;
        if( sz[ch[rtu][!id]]+sz[ch[rtv][!id]]-2*sz[ch[rtf][!id]] > 0)//滿足這個條件
        {
            ret += 1<<i;
            id = !id;
        }
        rtu = ch[rtu][id];
        rtv = ch[rtv][id];
        rtf = ch[rtf][id];
    }
    return max(ret,res);
}

int main()
{
    while( ~scanf("%d%d",&n,&q))
    {
        Init();
        for( int i = 1; i <= n; i++)
        {
            scanf("%d",&a[i]);//輸入每個結點的值
            g[i].clear();//清空動態陣列
        }
        int u,v;
        for( int i = 1; i < n; i++)
        {
            scanf("%d%d",&u,&v);//輸入一條邊
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs(1,0);

        for( int i = 0; i < maxv-1; i++)
            for( int j = 1; j <= n; j++)
                if( f[i][j] != 0)
                    f[i+1][j] = f[i][f[i][j]];//j向上跳2^(i+1)次得到的結點相當於j先向上跳2^i再向上跳2^i
        int x,y,z;
        while( q--)
        {
            scanf("%d%d%d",&x,&y,&z);
            printf("%d\n",Query(x,y,z));
        }
    }

    return 0;
}