[H字典树] lc1938. 查询最大基因差(trie总结+dfs离线搜索+离线处理+周赛250_4)

Posted Ypuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[H字典树] lc1938. 查询最大基因差(trie总结+dfs离线搜索+离线处理+周赛250_4)相关的知识,希望对你有一定的参考价值。

1. 题目来源

链接:1938. 查询最大基因差

相关题目:

2. 题目解析

一眼就是 trie。和 [H字典树] lc1707. 与数组中元素的最大异或值(trie+知识理解+离线处理) 套路差不多,都是需要离线查询,记录查询下标,然后返回。

朴素想法,对每个询问,都从 nodei 建立反遍历到根节点,求异或最大值即可,显然会超时,因为每次都需要新建 trie,每次都要重新求异或最大和。


思路:

  • 离线询问。将相同的 nodei 组织起来放到一起,valpair 记录值和下标,由于离线询问,需要按照下标顺序写回 res 数组。
  • 建树。父节点—>儿子节点,有向边,add(p[i],i) 即可。
  • dfs+trie。这里是 dfs,遇见节点就将将其插入到 trie 中,并判断当前节点是否是 nodei 的一个询问。
    • 注意: 这里的 dfs,这里的 trie 都是一条树中的链,在 trie 中保存的值,也仅仅是 dfs 路径上这一条链的值。
    • dfs 回溯的过程时,就说明当前节点已经没用了,后面也不会再用到了,所以就可以将当前节点从 trie 树中删除。保证 trie 树中的节点和 dfs 从根到当前节点上的路径节点是一致的,不会出现分叉节点同时存入 trie 中。 也就是 trie 树中暴搜的恢复现场工作。

trie 的相关操作有点生疏了,好久没写相关题了,本题还套上了暴搜 dfs 就很陌生了。

其实本题与 [H字典树] lc1707. 与数组中元素的最大异或值(trie+知识理解+离线处理) 非常相似,官方题解也证明该点了。

主要生疏点如下:

  • dfs 暴搜的处理。
  • 太依赖暴力解法,想建反向图,建立每个节点到根节点的邻接表。但是图太大,存不下,被卡死。
  • trie 的初始化也能搞错…误将其初始化为 -1,但显然我们判断存在不存在的时候用的是 而不是 ~

还是抽空多写写吧。


时间复杂度: O ( ( n + q ) 32 ) O((n+q)32) O((n+q)32)
空间复杂度: O ( n ∗ 32 ) O(n*32) O(n32)

代码:

#define x first
#define y second
typedef pair<int, int> PII;

const int N = 1e5+5, M = N * 31;

int root;
int h[N], e[M], ne[M], idx;
int s[M][2], cnt[M], idxs;
unordered_map<int, vector<PII>> hmp;
vector<int> res;

class Solution {
public:
    void add(int a, int b) {
        e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
    }

    // v=1 增加,v=-1 删除
    void insert(int x, int v) {
        int p = 0;
        for (int i = 30; ~i; i -- ) {
            int u = x >> i & 1;
            if (!s[p][u]) s[p][u] = ++ idxs;
            p = s[p][u];
            cnt[p] += v;
        }
    }

    int query(int x) {
        int res = 0, p = 0;
        for (int i = 30; ~i; i -- ) {
            int u = x >> i & 1;
            if (cnt[s[p][!u]]) res = res * 2 + 1, p = s[p][!u];
            else res = res * 2, p = s[p][u];
        }

        return res;
    }

    void dfs(int u) {
        insert(u, 1);
        if (hmp.count(u)) {
            for (auto &v : hmp[u]) res[v.y] = query(v.x);
        }
        
        for (int i = h[u]; ~i; i = ne[i]) dfs(e[i]);

        insert(u, -1);
    }
    
    vector<int> maxGeneticDifference(vector<int>& p, vector<vector<int>>& q) {
        memset(h, -1, sizeof h), idx = 0;
        memset(s, 0, sizeof s), idxs = 0;
        memset(cnt, 0, sizeof cnt);
        hmp.clear();

        // 离线询问
        for (int i = 0; i < q.size(); i ++ ) hmp[q[i][0]].push_back({q[i][1], i});

        // 建树
        for (int i = 0; i < p.size(); i ++ ) {
            if (~p[i]) add(p[i], i);            // p[i] 为父节点,i 为当前指向的子节点
            else root = i;
        }

        res.resize(q.size());
        dfs(root);
        
        return res;
    }
};

RE 代码,边太多,存不下的。即便存下了,在存图这一步就会达到 n^2 的时间复杂度,会 TLE没事真别去挑战一个没有意义的超时数据…

const int N = 5e5+5, M = N;
int h[N], e[M], ne[M], idx;

class Solution {
public:
    void add(int a, int b) {
        e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
    }
    
    vector<int> maxGeneticDifference(vector<int>& p, vector<vector<int>>& q) {
        memset(h, -1, sizeof h), idx = 0;
        
        int n = p.size();
        for (int i = 0; i < n; i ++ ) {
            int a = p[i];
            while (a != -1) {
                add(i, a);
                a = p[a];
            }
        }
    
        vector<int> res;
        for (auto &ee : q) {
            int t = ee[0], val = ee[1];
            int ans = 0;            // 自身异或
            for (int i = h[t]; ~i; i = ne[i]) {
                int j = e[i];
                ans = max(ans, j ^ val);
            }
            ans = max(ans, t ^ val);
            res.push_back(ans);
        }
        
        return res;
    }
};

以上是关于[H字典树] lc1938. 查询最大基因差(trie总结+dfs离线搜索+离线处理+周赛250_4)的主要内容,如果未能解决你的问题,请参考以下文章

hdu-1540(线段树+区间合并)

利用01字典树查询最大异或值

avl树

[H扫描线] lc218. 天际线问题(扫描线求轮廓+边界情况+好题+算法技巧)

最大边和最小边之差最小的生成树 UVA 1394

LeetCode每日一题——1707. 与数组中元素的最大异或值(字典树)