洛谷P2420 让我们异或吧 (树链剖分法)

本文介绍了一种利用树链剖分优化路径上权值异或查询的方法。通过对树进行重链剖分,将节点按dfs序映射到数组中,使用线性前缀和维护树链,实现单次查询O(logn)复杂度。文章详细解释了如何预处理到根的前缀和,以及如何通过异或操作高效地计算任意两点间的路径权值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

给出N<=100000节点的树,边上有权值,对于M<=100000次询问,输出其路径上所有权值的异或

分析

这题简单的做法其实是预处理到根的前缀,然后对于查询直接异或两个到root的权值即可。。。(加减法,在异或中都是异或),所以才是普及+级别。。。

先把运算当作加法:
u到v的路径即u→lca→v,延长这个路径,u→lca→root→lca→v,加减法情形,可以预处理root→lca,从而将重复段删除两次,这样发现对于这种问题只要求出所有节点到root的权值即可

对于异或,甚至更简单,u→root的权值xorv→root的权值就直接等于u→v的权值异或,这是因为重复段的异或会得0.
即u→lca→root→lca→v,红色部分在异或下得0,所以处理出所有节点到根的异或,查询时直接两者异或即可。

而这里一开始我被树链剖分误导了。。。。放了树链剖分的板子上去,但是结果也不差,最终用线性前缀和维护树链剖分得到的片段即可

和求LCA相比,多的部分就是,通过换dfs序,将重链上的数据都放在了一起,便于区间维护,加上轻重链均最多logn段,就做到了单次查询O(logn)的复杂度

取出这些链的操作详见下面calxor函数,将异或换为加法等其他运算就可以改变作用,但取链的方式仍然没变

代码

预处理:将线段上的权值放到节点上,节点的选择优先层深的(即最后会导致根上无权值,边上的权值都放到更向下的点上)

#include<iostream>
#include<vector>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<string>
using namespace std;

int A[100005];//用前缀和维护的数组
int preA[100005];//A前缀和
int B[100005];//原数组,经过dfs序映射后得到A
void sswap(int& a, int& b)
{
	int t = b;
	b = a;
	a = t;
}
class Tree_Chain {
private:
	const static int MAXN = 100005;
	int siz[MAXN];//表示其子树的节点数
	int fa[MAXN];//当前节点的父节点
	int son[MAXN];//节点的重儿子
	int top[MAXN];//重链顶
	int dep[MAXN];//当前节点深
	int l[MAXN];//dfs序
	std::vector<pair<int, int> >linker[MAXN];//存顶点存权值,后面的> >间要有空格
public:
	int dfs_clock = 0;
	void dfs1(int x)//x是当前节点(当前树根)
	{
		int cur;
		siz[x] = 1;
		for (int i = 0; i < linker[x].size(); i++)
		{
			cur = linker[x][i].first;
			if (cur != fa[x]) //不是fa
			{
				B[cur] = linker[x][i].second;//预处理,将边权放到点编号上
				dep[cur] = dep[x] + 1;//更新dep
				fa[cur] = x;//更新fa
				dfs1(cur);
				siz[x] += siz[cur];//更新siz
				if (siz[cur] > siz[son[x]])son[x] = cur;//更有可能成为重儿子
			}
		}
	}
	void dfs2(int x, int t)//当前节点x,当前重链顶的编号
	{
		l[x] = ++dfs_clock;//更新dfs序
		A[dfs_clock] = B[x];//换序映射,使重链节点在A上连续,A用前缀和维护
		top[x] = t;
		if (son[x])dfs2(son[x], t);//继续连重链
		int cur;
		for (int i = 0; i < linker[x].size(); i++)
		{
			cur = linker[x][i].first;
			if (cur != fa[x] && cur != son[x])//非父亲非重儿子
				dfs2(cur, cur);//在其他儿子上找更小一些的
		}
	}
	void insert(int& u, int& v, int& w)
	{
		linker[u].push_back(make_pair(v, w));
		linker[v].push_back(make_pair(u, w));
	}
	int Lca(int& u, int& v)
	{
		while (top[u] != top[v])
		{
			if (dep[top[u]] > dep[top[v]])u = fa[top[u]];
			else v = fa[top[v]];
		}
		return dep[u] < dep[v] ? u : v;
	}
	int calxor(int& u, int& v)//取轻/重链并在区间上处理
	{
		int ans = 0;
		while (top[u] != top[v])
		{
			if (dep[top[u]] < dep[top[v]])sswap(u, v);
			ans ^=( preA[l[u]] ^ preA[l[top[u]] - 1]);//l[top[u]]到l[u]是dfs序上连续的一段
			u = fa[top[u]];
		}
		if (dep[u] > dep[v])sswap(u, v);//到了一个重链上,切换到u比v浅的状态
		ans ^=( preA[l[v]] ^ preA[l[u]]);
		return ans;
	}
}TC;
int main()
{
	ios::sync_with_stdio(false);
	int n, u, v, temp, q;
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		cin >> u >> v >> temp;
		TC.insert(u, v, temp);
	}
	TC.dfs1(1);
	TC.dfs2(1, 1);
	for (int i = 2; i <= n; i++)
	{
		preA[i] = preA[i - 1] ^ A[i];
	}
	cin >> q;
	for (int i = 0; i < q; i++)
	{
		cin >> u >> v;
		cout << TC.calxor(u, v) << endl;
	}
	return 0;
}

Windows 系统修复工具主要用于解决 Windows 11/10 系统中的各种常见问题,具有操作简单、功能全面等特点: 文件资源管理器修复:可解决文件资源管理器卡死、崩溃、无响应等问题,能终止崩溃循环。还可修复右键菜单无响应或选项缺失问题,以及重建缩略图缓存,让图片、视频等文件的缩略图正常显示,此外,还能处理桌面缺少回收站图标、回收站损坏等问题。 互联网和连接修复:能够刷新 DNS 缓存,加速网页加载速度,减少访问延迟。可重置 TCP/IP 协议栈,增强网络连接稳定性,减少网络掉线情况,还能还原 Hosts 文件,清除恶意程序对网络设置的篡改,保障网络安全,解决电脑重装系统后网络无连接、浏览器主页被篡改等问题。 系统修复:集成系统文件检查器(SFC),可自动扫描并修复受损的系统文件。能解决 Windows 激活状态异常的问题,还可重建 DLL 注册库,恢复应用程序兼容性,解决部分软件无正常运行的问题,同时也能处理如 Windows 沙箱无启动、Windows 将 JPG 或 JPEG 保存为 JFIF 等系统问题。 系统工具维护:提供启动管理器、服务管理器和进程管理器等工具,用户可控制和管理启动程序、系统服务和当前运行的进程,提高系统的启动和运行速度,防止不必要的程序和服务占用系统资源。还能查看系统规格,如处理器线程数、最大显示分辨率等。 故障排除:集成超过 20 个微软官方诊断工具,可对系统问题进行专业排查,还能生成硬件健康状态报告。能解决搜索和索引故障、邮件和日历应用程序崩溃、设置应用程序无启动等问题,也可处理打印机、网络适配器、Windows 更新等相关故障。 其他修复功能:可以重置组策略设置、catroot2 文件夹、记事本等多种系统设置和组件,如重置 Windows 应用商店缓存、Windows 防火墙设置等。还能添加重建图标缓存支持,恢复粘滞便笺删除
洛谷P4592,请分析一下这段代码WA40pts的原因: #include <bits/stdc++.h> using namespace std; const int BITS = 32; const int MAX_N = 1e5 + 50; int n, q, val[MAX_N], fa[MAX_N], tim; int dep[MAX_N], siz[MAX_N], son[MAX_N]; int top[MAX_N], dfn[MAX_N], rev[MAX_N]; vector<int> e[MAX_N]; void dfs1(int u, int father, int depth) { fa[u] = father; dep[u] = depth; siz[u] = 1; for (int v : e[u]) { if (v == father) continue; dfs1(v, u, depth + 1); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } void dfs2(int u, int topf) { top[u] = topf; dfn[u] = ++tim; rev[tim] = u; if (son[u]) dfs2(son[u], topf); for (int v : e[u]) if (!dfn[v]) dfs2(v, v); } int root[MAX_N], tot; struct Node { int ch[2], cnt; Node() {ch[0] = ch[1] = cnt = 0; } } tr[MAX_N * BITS]; int insert(int pos, int x, int k) { int cur = ++tot, t = x >> k & 1; tr[cur] = tr[pos], tr[cur].cnt++; if (k < 0) return cur; tr[cur].ch[t] = insert(tr[cur].ch[t], x, k - 1); return cur; } int query(int u, int v, int x, int k) { if (k < 0) return 0; int t = x >> k & 1; if (tr[tr[v].ch[t ^ 1]].cnt - tr[tr[u].ch[t ^ 1]].cnt) return query(tr[u].ch[t ^ 1], tr[v].ch[t ^ 1], x, k - 1) + (1 << k); return query(tr[u].ch[t], tr[v].ch[t], x, k - 1); } int query(int l, int r, int x) { return query(root[l - 1], root[r], x, BITS - 1); } int tree_query(int x, int z) { return query(dfn[x], dfn[x] + siz[x] - 1, z); } int path_query(int x, int y, int z) { int ans = 0; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); ans = max(ans, query(dfn[top[x]], dfn[x], z)); x = fa[top[x]]; } if (dep[x] < dep[y]) swap(x, y); ans = max(ans, query(dfn[y], dfn[x], z)); return ans; } int main() { cin >> n >> q; for (int i = 1; i <= n; i++) cin >> val[i]; for (int i = 1, u, v; i < n; i++) { cin >> u >> v; e[u].push_back(v); e[v].push_back(u); } dfs1(1, 0, 1); dfs2(1, 1); for (int i = 1; i <= n; i++) root[i] = insert(root[i - 1], val[rev[i]], BITS - 1); for (int opt, x, y, z; q--; ) { cin >> opt; if (opt == 1) { cin >> x >> z; cout << tree_query(x, z) << '\n'; } else { cin >> x >> y >> z; cout << path_query(x, y, z) << '\n'; } } return 0; }
07-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值