SPOJ - COT Count on a tree(树上主席树 求树中路径第 k 小权值 + 倍增 LCA)

前置知识:普通主席树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题意:

给定 一个包含 N 个结点的树 树节点 1N 编号。每个节点有一个 整数权值

要求执行 以下操作

u v k:询问 从节点 u 到 节点 v 的路径上k 小的权值

思路:

在普通主席树 求静态数组第 k 小值 时,我们在 for 循环 建树,并利用前缀和的思想每次查询 某段区间的第 k 小值

普通主席树 维护的其实是一个 线性表的前缀和,但前缀和不一定要出现在线性表上,即:对于 树中 每个 从根到点 u 的路径 是一个 线性序列,我们也可以 把这个序列建成主席树

显然本题是 求树上任意路径中的第 k 小权值,为了实现上面这点,我们需要引入一个 新概念树上主席树

普通区间主席树树上主席树 的区别:

这两个其实只有 建树方式不同 而已,普通主席树在 for 循环里面建树for 循环 就相当于 线性的区间建树,而 树上主席树是在树形结构上建主席树,建出来的主席树有 树的性质,所以我们要求 任意两点之间最短路径上的第 k,就需要用到 lca(至于为什么,后面有解释),在 树上建树 其实就是 dfs 遍历树的时候 我们将 当前版本上一版本 进行 复制,修改,而 上一版本 就是 父节点

树上建主席树 代码:

void dfs(int u, int f)
{
	root[u] = insert(root[f], 0, nums.size() - 1, find(w[u]));	//最新版本:root[u] 上一版本:root[f]
	for (int i = 0; i < g[u].size(); ++i)
	{
		int v = g[u][i];
		if (v == f) continue;
		dfs(v, u);
	}
}

维护 树上任意路径第 k 小权值 的具体操作,结合图例来分析:

在这里插入图片描述
(上图中,0 是自己添加的作为 节点 1 的父节点

现在,我们要 查询 23 这条路径上的第 k 小权值

由于在 dfs 建树 的时候由于已经分别保存了 02 和从 03 的前缀信息,所以 2,3 之间的路径信息 就可以用 t[2].cnt + t[3].cnt - t[lca(2, 3)] - t[fa[lca(2, 3)]] 这个式子来求得,其中 lca(2, 3) 表示 节点 2 和节点 3 的最近公共祖先fa[lca] 表示 lca 的父节点

解释:在 t[2].cnt + t[3].cnt 中,t[lca(2, 3)] 被加了 两遍,因此要 - t[lca(2, 3)],之后类比 前缀和 的思想 [l, r] = sum[r] - sum[l - 1],最后要 - t[fa[lca(2, 3)]]

所以,对于 一般的 u , v 路径之间的信息 就可以用 t[u].cnt + t[v].cnt - t[lca(u, v)] - t[fa[lca(u, v)]].cnt 进行查询。

深搜、递推 预处理 + 树上倍增求 lca 板子(简化版 更好用 更好记):

const int N = 1e5 + 10;
int n, m;	//n 个点 m 次询问

//LCA
vector<int> g[N];	//存树
int fa[N][18], depth[N];
int LOG;

void dfs(int u, int f)
{
	fa[u][0] = f;
	depth[u] = depth[f] + 1;
	for (int j = 1; j <= LOG; ++j) {
		fa[u][j] = fa[fa[u][j - 1]][j - 1];
	}
	for (int i = 0; i < g[u].size(); ++i)
	{
		int v = g[u][i];
		if (v == f) continue;
		dfs(v, u);
	}
}

int lca(int x, int y)
{
	if (depth[x] < depth[y]) swap(x, y);
	int dc = depth[x] - depth[y];
	for (int k = 0; k <= LOG; ++k) {
		if ((1 << k) & dc) x = fa[x][k];
	}
	if (x == y) return x;
	for (int k = LOG; k >= 0; --k) {
		if (fa[x][k] != fa[y][k]) {
			x = fa[x][k];
			y = fa[y][k];
		}
	}
	x = fa[x][0];
	return x;
}

/*
int main(){
	cin >> n >> m;
	
	//此处完成建树操作:blablabla...
	
	LOG = int(log(n + 0.0) / log(2.0)) + 1;
	dfs(1, 0);
	while (m--)
	{
		int a, b; scanf("%d%d", &a, &b);
		printf("%d\n", lca(a, b));
	}
	return 0;
}
*/

注意点权 需要开 long long

时间复杂度:

O ( m l o g n ) O(mlogn) O(mlogn)

代码:

#include <bits/stdc++.h>

using namespace std;
//#define map unordered_map
//#define int long long
#define ll long long
const int N = 1e5 + 10, M = 1e5 + 10;
const int ALL = (N << 2) + M * 17;
int n, m;
vector<int> g[N];

//HJT Tree
ll w[N];
vector<ll> nums;
int root[ALL];

struct node
{
	int l, r;
	int cnt;
} t[ALL];
int idx;

int find(int x) {
	return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

int build(int l, int r) {
	int p = ++idx;
	if (l == r) {
		return p;
	}
	int mid = l + r >> 1;
	t[p].l = build(l, mid), t[p].r = build(mid + 1, r);
	return p;
}

int insert(int p, int l, int r, int x)
{
	int q = ++idx;
	t[q] = t[p];
	if (l == r)
	{
		++t[q].cnt;
		return q;
	}
	int mid = l + r >> 1;
	if (x <= mid) t[q].l = insert(t[p].l, l, mid, x);
	else t[q].r = insert(t[q].r, mid + 1, r, x);
	t[q].cnt = t[t[q].l].cnt + t[t[q].r].cnt;
	return q;
}

int ask(int q, int p, int lca, int fa_lca, int l, int r, int x)
{
	if (l == r) return r;
	int cnt = t[t[q].l].cnt + t[t[p].l].cnt - t[t[lca].l].cnt - t[t[fa_lca].l].cnt;
	int mid = l + r >> 1;
	if (x <= cnt) return ask(t[q].l, t[p].l, t[lca].l, t[fa_lca].l, l, mid, x);
	else return ask(t[q].r, t[p].r, t[lca].r, t[fa_lca].r, mid + 1, r, x - cnt);
}

//LCA
int fa[N][18], depth[N];
int LOG;

void dfs(int u, int f)
{
	fa[u][0] = f;
	depth[u] = depth[f] + 1;
	for (int j = 1; j <= LOG; ++j) {
		fa[u][j] = fa[fa[u][j - 1]][j - 1];
	}
	root[u] = insert(root[f], 0, nums.size() - 1, find(w[u]));
	for (int i = 0; i < g[u].size(); ++i)
	{
		int v = g[u][i];
		if (v == f) continue;
		dfs(v, u);
	}
}

int lca(int x, int y)
{
	if (depth[x] < depth[y]) swap(x, y);
	int dc = depth[x] - depth[y];
	for (int k = 0; k <= LOG; ++k) {
		if ((1 << k) & dc) x = fa[x][k];
	}
	if (x == y) return x;
	for (int k = LOG; k >= 0; --k) {
		if (fa[x][k] != fa[y][k]) {
			x = fa[x][k];
			y = fa[y][k];
		}
	}
	x = fa[x][0];
	return x;
}

signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld", &w[i]);
		nums.push_back(w[i]);
	}
	int t = n - 1;
	while (t--)
	{
		int u, v; scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}

	sort(nums.begin(), nums.end());
	nums.erase(unique(nums.begin(), nums.end()), nums.end());

	root[0] = build(0, nums.size() - 1);

	LOG = int(log(n + 0.0) / log(2.0)) + 1;
	dfs(1, 0);	//lca深搜预处理 与此同时建树上主席树

	while (m--)
	{
		int u, v, k;
		scanf("%d%d%d", &u, &v, &k);
		int LCA = lca(u, v);
		int ans = ask(root[v], root[u], root[LCA], root[fa[LCA][0]], 0, nums.size() - 1, k);
		printf("%lld\n", nums[ans]);
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值