[H字典树] lc1938. 查询最大基因差(trie总结+dfs离线搜索+离线处理+周赛250_4)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[H字典树] lc1938. 查询最大基因差(trie总结+dfs离线搜索+离线处理+周赛250_4)相关的知识,希望对你有一定的参考价值。
1. 题目来源
相关题目:
- 首先 [Trie] lc421. 数组中两个数的最大异或值(Trie+Trie的不同写法+算法对比)
- [字典树] 最大异或对(trie+贪心)
- 进阶一,务必掌握:力扣上与其相关的问题,离线查询。[H字典树] lc1707. 与数组中元素的最大异或值(trie+知识理解+离线处理)
- 进阶二,务必掌握:记录节点数,支持增加、删除
trie
中的节点,离线查询必备。[字典树] aw3485. 最大异或和(trie变种+贪心+前缀和+滑动窗口+美团2021)
2. 题目解析
一眼就是 trie
。和 [H字典树] lc1707. 与数组中元素的最大异或值(trie+知识理解+离线处理) 套路差不多,都是需要离线查询,记录查询下标,然后返回。
朴素想法,对每个询问,都从 nodei
建立反遍历到根节点,求异或最大值即可,显然会超时,因为每次都需要新建 trie,每次都要重新求异或最大和。
思路:
- 离线询问。将相同的
nodei
组织起来放到一起,val
用pair
记录值和下标,由于离线询问,需要按照下标顺序写回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(n∗32)
代码:
#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)的主要内容,如果未能解决你的问题,请参考以下文章