zoukankan      html  css  js  c++  java
  • HDU 4757 Tree

    传送门

    Tree

    Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 102400/102400 K (Java/Others)


    Problem Description
      Zero and One are good friends who always have fun with each other. This time, they decide to do something on a tree which is a kind of graph that there is only one path from node to node. First, Zero will give One an tree and every node in this tree has a value. Then, Zero will ask One a series of queries. Each query contains three parameters: $x, y, z$ which mean that he want to know the maximum value produced by $z$ xor each value on the path from node $x$ to node $y$ (including node $x$, node $y$). Unfortunately, One has no idea in this question. So he need you to solve it.
     
    Input
      There are several test cases and the cases end with EOF. For each case:

      The first line contains two integers $n$ ($1le n le 10^{5}$) and $m$ ($1 le m le 10^{5}$), which are the amount of tree’s nodes and queries, respectively.

      The second line contains $n$ integers $a_1, a_2, dots, a_n$ and $a_i$ ($0 le a_i <2^{16}$) is the value on the $i$th node.

      The next $n–1$ lines contains two integers $u, v$, which means there is an edge between $u$ and $v$.

      The next $m$ lines contains three integers $x ,y, z$, which are the parameters of Zero’s query.
     
    Output
      For each query, output the answer.
     
    Sample Input
    3 2
    1 2 2
    1 2
    2 3
    1 3 1
    2 3 2
     
    Sample Output
    3
    0
     
    Source

    Solution
    可持久化 trie 。
    trie可以解决关于异或的一个经典问题:
    给定非负整数序列 $a_1,cdots, a_n$ 和非负整数 $x$ 求
    [max_{1 le i le n} a_i ext{^} x]
    做法:
    将 $a_1$ 到 $a_n$ 的二进制形式(二进制串, 高位在前)插入到 trie 里, 用 $x$ 的二进制串在建好的 trie 里贪心匹配.
    这种做法可推广到这个题目上:
    我们对每个节点 $u$ 建立一棵 trie ,里面存的是从根节点到 $u$ 的路径上的所有节点的权值的二进制串.
    先不考虑如何建这 $n$ 棵 trie ,我们考虑如何利用这 $n$ 棵 tire 完成题目要求的查询.
    我们用 $ ext{lca}(u,v)$ 表示 $u,v$ 的最近公共祖先(LCA), $ ext{trie}_u$ 表示节点 $u$ 对应的 trie 。
    对于询问 $(u, v, x)$ ,以 $x$ 的二进制串在 $ ext{trie}_u, ext{trie}_v$ 和 $ ext{trie}_{ ext{lca}(u,v)}$ 中同步贪心匹配.
    注意: 在贪心匹配的每个阶段, 我们匹配到的都是某个数的二进制串的某个前缀, 这个前缀属于哪个数是不知道的, 那么如何保证最后匹配到的那个二进制串对应的数出现在 $u$ 到 $v$ 的路径上呢?
     
    可以这样做:
    首先应当明确: tire 的每个节点都代表一个二进制串(二进制前缀). 我们对 tire 的每个节点维护一个值 $cnt$ 表示在这棵 tire 里有多少个(完整的)二进制串包含该节点对应的前缀(即该二进制前缀出现的次数).
     
    这样欲匹配某个前缀时, 我们就能判断该前缀是否(也)属于$u$到$v$的路径上某个节点的权值对应的二进制串. 只有当该前缀合法时才能继续匹配.
     
    具体而言, 设目前要匹配的二进制前缀在 $ ext{trie}_u, ext{trie}_v$ 和 $ ext{trie}_{ ext{lca}(u,v)}$ 分别对应于节点 $p,q,r$, 则要求:[p.cnt+q.cnt>2 imes r.cnt]
     
    这样做有一个潜在的 bug:
    当 $a_{ ext{lca}(u,v)}$ 是唯一最优解时, 会被忽略.  所以最后还要用 $x ext{^}a_{ ext{lca}(u,v)}$ 更新答案.
     
    当然这个麻烦也是可应避免的, 只要把 $ ext{trie}_{ ext{lca}(u,v)}$ 换成 $ ext{trie}_{ ext{par}( ext{lca}(u,v))}$ —— $ ext{par}(u)$ 表示 $u$ 的父节点——就可以了.
     
    现在考虑建 $n$ 棵 trie 的问题. 我们没必要分别建 $n$ 棵 trie, 而且这样做空间和时间都无法承受. 考虑到 $ ext{trie}_u$ 与 $ ext{trie}_{ ext{par}(u)}$ 的差别只是多插入了 $a_u$ 对应的二进制串, 我们可以采用可持久化 trie 来建这 $n$ 棵树. (如果读者还不了解可久化数据结构, 可参考陈立杰的《可持久化数据结构研究》的第 4 节——可持久化线段树.)

    Implementation

    可持久化 trie 在写法上有一个技巧: 我们一开始用一个节点表示一棵空树, 将此节点的 $cnt$ 置为 $0$. 在建树的过程中如果某个节点的某个儿子为空, 对应的指针就指向该空节点.
    这样写起来就方便很多, 而且不会有 bug.
    #include <bits/stdc++.h>
    using namespace std;
    
    const int M{1<<21}, N{1<<17};
    
    int a[N], nxt[M][2], cnt[M], tail, root[N];
    vector<int> g[N];
    
    //a NULL NODE
    //The empty tree is a well-defined tree!
    
    int Insert(int id, int v, int dep){ //this node is going to be modified
        //always copy before modification
        int cur=tail++;
        cnt[cur]=cnt[id]+1;
        if(dep>=0)
            if(v&1<<dep){
                nxt[cur][0]=nxt[id][0];
                nxt[cur][1]=Insert(nxt[id][1], v, dep-1);
            }
            else{
                nxt[cur][0]=Insert(nxt[id][0], v, dep-1);
                nxt[cur][1]=nxt[id][1];
            }
        else nxt[cur][0]=nxt[cur][1]=0; //error-prone
    
        return cur;
    }
    
    int fa[N][17], dep[N];
    
    void dfs(int u, int f){
        fa[u][0]=f, dep[u]=dep[f]+1;
        for(int i=1; i<17; i++)
            fa[u][i]=fa[fa[u][i-1]][i-1];
    
        root[u]=Insert(root[f], a[u], 15);
        for(auto x:g[u])
            if(x!=f)
                dfs(x, u);
    }
    
    int get_lca(int u, int v){
        if(dep[u]<dep[v]) swap(u, v);
        int diff=dep[u]-dep[v];
        for(int i=0; i<17; i++)
            if(diff&1<<i) u=fa[u][i];
        if(u==v) return u;
        for(int i=16; i>=0; i--)    //error-prone
            if(fa[u][i]!=fa[v][i])
                u=fa[u][i], v=fa[v][i];
        return fa[u][0];
    }
    
    //keep it simple
    int query(int u, int v, int x){
        int res=0, lca=get_lca(u, v);
        int _u=root[u], _v=root[v], _w=root[lca];
    
        for(int i=15; i>=0; i--){
            bool f=!(x&1<<i);
            //check if this bit valid
            if(cnt[nxt[_u][f]]+cnt[nxt[_v][f]]>cnt[nxt[_w][f]]<<1) res|=1<<i;
            else f=!f;
            _u=nxt[_u][f], _v=nxt[_v][f], _w=nxt[_w][f];
        }
        return max(res, x^a[lca]);
    }
    
    int main(){
        for(int n, m; cin>>n>>m; ){
            for(int i=1; i<=n; i++){
                scanf("%d", a+i);
                g[i].clear();
            }
            for(int u, v, i=1; i<n; i++){
                scanf("%d%d", &u, &v);
                g[u].push_back(v);
                g[v].push_back(u);
            }
            //construct an empty tree
            root[0]=tail=0, memset(nxt[tail], 0, sizeof(nxt[tail])), cnt[tail]=0, tail++;
    
            dfs(1, 0);
            for(int u, v, x, lca; m--; ){
                scanf("%d%d%d", &u, &v, &x);
                printf("%d
    ", query(u, v, x));
            }
        }
    }
     
     
     
     
     
     
     
     
  • 相关阅读:
    菜鸟涂鸦作品展_No.29
    菜鸟涂鸦作品展_No.27
    菜鸟涂鸦作品展_No.26
    关注的前沿动态
    货币转换
    第二周总结
    老鼠
    71 打印沙漏
    学习总结0.0
    第五周作业
  • 原文地址:https://www.cnblogs.com/Patt/p/5773652.html
Copyright © 2011-2022 走看看