其他分享
首页 > 其他分享> > 洛谷P8201 生活在树上(Hard Version) 题解

洛谷P8201 生活在树上(Hard Version) 题解

作者:互联网

题目链接:P8201 生活在树上(hard version)

题意

给定一个点带权的树,点数为 \(n\),第 \(i\) 个点的权值为 \(w_i\)。

定义两点之间的路径为路径上所有点的点权的按位异或和,即 \(dis(a,b)\)。

现在有 \(m\) 次询问,每次询问给定三个数 \(x,y,k\),问是否存在 \(t\),使得 \(dis(t,x)\oplus dis(t,y)=k\)?

\(n,m\leq 5*10^5,0\leq k,w_i\leq 10^7\)

简化与分析

这题有一个简单版,就是把点权转化成了边权,只要重复的一去掉,就看 \(dis(x,y)\) 的值是否为 \(k\) 即可。

现在考虑一下点权,考虑到 \(t\) 的各种情况,可得:

  1. \(lca(x,y)=x\)
  2. \(lca(x,y)=y\)
  3. \(lca(x,y)=t(t\not=x,y)\)

不管咋样,划分完各种情况后,我们发现,当 \((x,y)\) 路径上存在一个点 \(t\),使得 \(w_t\oplus dis(x,y)=k\) 时,询问成立。

\(dis(x,y)\) 可以直接一次 dfs 之后推出 \(S\) 数组,随后就转化为了 \(dis(x,y)=S_x\oplus S_y\oplus w_{lca}\)。

那么,问题就转化为了:\((x,y)\) 上是否存在点 \(t\),满足 \(w_t=W=S_x\oplus S_y\oplus w_{lca}\oplus k\)。

树上差分

LCA求法

这题在题面里面表明了要卡常数,所以直接 Tarjan 就行了(反之是离线)。

点的计数查询

询问是离线的,所以不必在线求。我们假设 \(lca(x,y)=t\),\(t\) 的父节点为 \(t'\),那么 \((x,y)\) 上符合要求的节点数为 \((root,x)+(root,y)-(root,t)-(root,t')\)。

我们记某次查询为 \((x,W,f)\) 为查询根节点到点 \(x\) 上权值为 \(W\) 的个数,\(f\) 标记要不要取反,那么一共有 \(4m\) 次查询。而他们是可以离线之后统一存下来,随后一次 DFS 时全部处理好(说不上来,感觉像 HH的项链 啥的题目也用了这种技巧),伪代码如下:

//w < 1e7,所以异或后至多是两倍大小
int vis[2e7+10];
void dfs(int x) {
    vis[w[x]]++;
    for (Query q : query[x])
        q.ans = q.f * vis[q.W];
    for (int y : son[x])
        dfs(y);
    vis[w[x]]--;
}

复杂度分析

主要的复杂度在于求 LCA 部分,考虑到 Tarjan 求 LCA 的复杂度在于并查集,所以复杂度视情况而已,从 \(O(n\log n)\) 到 \(O(n\alpha(n))\) 都有可能。后者只需要离线之后统一 \(O(n)\) 进行一次深搜即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n, m, w[N];
// Graph
vector<int> G[N];
//Query
struct Query { int x, y, k, lca, f_lca, id; };
vector<Query> query;
namespace Tarjan_LCA {
	int fa[N];
	void init() { for (int i = 1; i <= n; ++i) fa[i] = i; }
	int find(int x) {
		if (x != fa[x]) fa[x] = find(fa[x]);
		return fa[x];
	}
	void merge(int x, int y) { fa[find(x)] = find(y); }
	//Tarjan
	vector<int> Q[N];
	int vis[N], father[N];
	void Tarjan(int x, int f) {
		vis[x] = 1, father[x] = f;
		for (int y : G[x])
			if (y != f) Tarjan(y, x), merge(y, x);
		for (int id : Q[x]) {
			Query &q = query[id];
			int y = x ^ q.x ^ q.y;
			if (vis[y])
				q.lca = find(y), q.f_lca = father[q.lca];
		}
	}
	void solve() {
		for (Query q : query)
			Q[q.x].push_back(q.id), Q[q.y].push_back(q.id);
		init();
		Tarjan(1, 0);
	}
}
//dfs1
int S[N];
void dfs1(int x, int f) {
	S[x] = S[f] ^ w[x];
	for (int y : G[x]) if (y != f) dfs1(y, x);
}
//dfs2
int vis_S[20000010];
struct Query2 { int id, W, f; };
vector<Query2> qq[N];
int ans[N];
void dfs2(int x, int f) {
	++vis_S[w[x]];
	for (Query2 q : qq[x])
		ans[q.id] += vis_S[q.W] * q.f;
		for (int y : G[x])
			if (y != f) dfs2(y, x);
	--vis_S[w[x]];
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		scanf("%d", &w[i]);
	for (int i = 1; i < n; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		G[x].push_back(y), G[y].push_back(x);
	}
	for (int i = 0; i < m; ++i) {
		int x, y, k;
		scanf("%d%d%d", &x, &y, &k);
		query.push_back((Query){x, y, k, -1, -1, i});
	}
	Tarjan_LCA::solve();
	dfs1(1, 0);
	for (Query q : query) {
		int W = S[q.x] ^ S[q.y] ^ w[q.lca] ^ q.k;
		qq[q.x    ].push_back((Query2){q.id, W,  1});
		qq[q.y    ].push_back((Query2){q.id, W,  1});
		qq[q.lca  ].push_back((Query2){q.id, W, -1});
		qq[q.f_lca].push_back((Query2){q.id, W, -1});
	}
	dfs2(1, 0);
	for (int i = 0; i < m; ++i)
		puts(ans[i] > 0 ? "Yes" : "No");
	return 0;
}

标签:int,题解,Hard,vis,Version,lca,oplus,id,dis
来源: https://www.cnblogs.com/cyhforlight/p/16452536.html