Query on A Tree(可持续01线段树+dfs序)

6 篇文章 0 订阅
4 篇文章 0 订阅

Link

题意

给你一棵树,每个节点有权值,Q次询问,求u为跟的子树一个点与x亦或后的值最大是多少。

思路

对于异或我们很容易联想到异或, 但显然对于每个点都需要开一个01字典树, 但如果每个点都开一个01字典树来表示其子树,显然空间不够, 所以引出了可持续化01字典树,和主席树类似。
所以我们先按照前序遍历建立可持续化01字典树
然后对于每个查询 u, x
l = st[u], r = st[u];
我们用 root[l - 1]这个01字典树和root[r], 来对比出以u为根的字典树,然后直接查询最大异或值即可。
细节见于代码

#include <bits/stdc++.h>

using namespace std;

#define ll long long
const ll N = 260005;
vector<int> q[N];
int n, m, t, a[N], id[N], rt[N], st[N], ed[N], b[N];

struct node 
{
	int cnt;
	int ch[N * 32][2], sum[N * 32];
	void init()
	{
		cnt = 0, b[0] = 1; // b[i], 记录第2^i
		for(int i = 1; i <= 30; i ++) b[i] = b[i - 1] * 2;
		memset(ch, 0, sizeof ch);
		memset(sum, 0, sizeof sum);
	}
	int insert_(int x, int val) // x为上一个trie树的根节点
	{
		int res, y;
		y = res = ++ cnt; // 开新点
		for(int i = 30; i >= 0; i --)
		{
			ch[y][0] = ch[x][0];
			ch[y][1] = ch[x][1];
			sum[y] = sum[x] + 1;
			int tmp = val & b[i];
			tmp >>= i; // 求出val在二进制下第i位的值
			x = ch[x][tmp]; // x(tmp方向)向下走
			ch[y][tmp] = ++ cnt; // 开点
			y = ch[y][tmp]; // y也继续向下走
		}
		sum[y] = sum[x] + 1; // 到叶子节点后, 表示在同一个位置来过的次数
		return res;
	}
	int query(int l, int r, int val)
	{
		int ans = 0;
		for(int i = 30; i >= 0; i --)
		{
			int tmp = val&b[i];
			tmp >>= i; // 取val的第i位(二进制)
			if(sum[ch[r][tmp ^ 1]] - sum[ch[l][tmp ^ 1]])
				ans += b[i], l = ch[l][tmp ^ 1], r = ch[r][tmp ^ 1];
			else
				l = ch[l][tmp], r = ch[r][tmp];
		}
		return ans;
	}
}trie;

void dfs(int u, int p) //
{
	st[u] = ++ t;//  存子树的前序遍历的起点
	id[t] = u; // 存前序遍历结果
	for(int i = 0; i < q[u].size(); i ++)
	{
		int v = q[u][i];
		if(v == p)
			continue;
		dfs(v, u);
	}
	ed[u] = t; // 存以u为根节点,子树前序遍历的终点
}

int main()
{
	int i, j, x;
	while(scanf("%d%d", &n, &m) != EOF)
	{
		t = 0;
		rt[0] = 0;
		trie.init();
		for(int i = 1; i <= n; i++)
			scanf("%d", &a[i]), q[i].clear();
		for(i = 2; i <= n; i ++)
		{
			scanf("%d", &x);
			q[x].push_back(i);
		}
		dfs(1, 0);
		for(int i = 1; i <= n; i++)
			rt[i] = trie.insert_(rt[i - 1], a[id[i]]); // 按照前序遍历,分别对每一个前序遍历的节点建一棵01字典树
		int l, r, root, x;
		while(m --)
		{
			scanf("%d%d", &root, &x);
			l = st[root];
			r = ed[root];
			printf("%d\n", trie.query(rt[l - 1], rt[r], x)); // 只需查询l ~ r 中那个
		}
	}
	return 0;
}

在C++中,可持续化(Lazy Propagation)也称为延迟更新(Lazy Evaluation),通常与线段树(Segment Tree)结合使用,这是一种高效的数据结构,用于支持区间查询和修改操作。线段树常用于解决范围查询问题,如计算区间的和、积、最大值、最小值等问题。 **线段树(Segment Tree)**是一种自底向上的数据结构,通过分治思想将原始数组分解成若干个连续的子区间,并为每个区间维护一个总结算结果。它能够快速地对区间进行查询和修改操作。 **可持续化/延迟更新(Lazy Propagation)**的概念允许在线段树上执行某些昂贵的操作(比如计算平方或更新某个元素)时只影响到需要的部分,而不是立即应用到整个树。这样可以避免不必要的重复计算,提高效率。具体来说,在线段树中,当遇到需要更新操作时,不会立刻改变该节点的结果,而是将其标记为待更新,然后在之后的访问过程中再进行更新。 下面是一个简单的线段树示例(不涉及懒惰更新)[^4]: ```cpp struct Node { int val; int lazy; // 延迟更新标记 // ... }; void updateRange(Node &node, int start, int end, int new_val) { node.val = ...; // 更新操作 if (start != end) { // 如果不是叶子节点 node.lazy = ...; // 分配给子节点 } } ``` 要实现懒惰更新,我们需要在`updateRange`和`query`函数中加入逻辑来处理`lazy`标志。相关问题如下: 1. 如何在C++线段树中实现懒惰更新? 2. 懒惰更新主要用于优化哪种类型的查询或修改操作? 3. 是否有其他术语描述这种结合线段树和懒惰更新的数据结构?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值