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

题面

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

分析

这题简单的做法其实是预处理到根的前缀,然后对于查询直接异或两个到root的权值即可。。。(加减法,在异或中都是异或),所以才是普及+级别。。。
洛谷P2420 让我们异或吧 (树链剖分法)_第1张图片
先把运算当作加法:
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
#include
#include
#include
#include
#include
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;
}

你可能感兴趣的:(#,树)