P5838 [USACO19DEC]Milk Visits G (树链剖分 + 主席树)

题目链接: P5838 [USACO19DEC]Milk Visits G

大致题意

给定一棵有 n n n个节点的树, 每个节点有权值 w i w_i wi, 有 m m m次询问:

a b c 询问 a , b a, b a,b路径上是否有 w i = = c w_i == c wi==c的节点.

解题思路

树链剖分 + 主席树

我们首先考虑如果不是树上路径问题, 而是询问 [ l , r ] [l, r] [l,r]区间中, 是否有 w i = = c w_i == c wi==c的点.

我们可以采用可持久化线段树来实现.

对于树上路径问题, 我们加上树链剖分即可.


特别的, 建树应当对于 d f s dfs dfs序去建树, 每次树上路径查询时, 同样需要对左端点去 − 1 -1 1.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
int n, m;
int w[N]; //给定的树中各个顶点的权值
vector<int> edge[N]; //树上各个点之间的边

int p[N], dep[N], sz[N], son[N];
// 父节点   深度   节点大小 重儿子
void dfs1(int x = 1, int fa = 0) { // x = 树根节点
	p[x] = fa, dep[x] = dep[fa] + 1, sz[x] = 1; // son[x] = 0;
	for (auto& to : edge[x]) {
		if (to == fa) continue;
		dfs1(to, x);
		sz[x] += sz[to];		// 特别的, 如果边权->点权, 应记录w[to] = 边权.
		if (sz[to] > sz[son[x]]) son[x] = to; //更新重儿子
	}
}
int id[N], nw[N], top[N], ind;
//  新编号  新值    重链顶 当前用到的编号
void dfs2(int x = 1, int tp = 1) { // x = 树根节点, tp = 树根节点
	id[x] = ++ind, nw[ind] = w[x], top[x] = tp;

	if (!son[x]) return; //叶子结点
	dfs2(son[x], tp); //先遍历重儿子

	for (auto& to : edge[x]) {
		if (to == p[x] or to == son[x]) continue;
		dfs2(to, to);
	}
}


namespace {
	struct node {
		int l, r;
		int cou;
	}t[N * 18];
	int root[N], ind;
	int build(int a, int c, int tl, int tr, int p) {
		int x = ++ind; t[x] = t[p];
		t[x].cou += c;
		if (tl == tr) return x;
		int mid = tl + tr >> 1;
		if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
		else t[x].r = build(a, c, mid + 1, tr, t[p].r);
		return x;
	}
	int ask(int a, int tl, int tr, int p, int x) {
		if (tl == tr) return t[x].cou - t[p].cou;
		int mid = tl + tr >> 1;
		if (a <= mid) return ask(a, tl, mid, t[p].l, t[x].l);
		return ask(a, mid + 1, tr, t[p].r, t[x].r);
	}
}

bool ask_route(int a, int b, int c) {
	while (top[a] != top[b]) {
		if (dep[top[a]] < dep[top[b]]) swap(a, b);
		int r = id[a], l = id[top[a]];
		if (ask(c, 1, n, root[l - 1], root[r])) return 1;
		a = p[top[a]];
	}

	if (dep[a] > dep[b]) swap(a, b);
	return ask(c, 1, n, root[id[a] - 1], root[id[b]]);
}
char res[N];
int main()
{
	cin >> n >> m;
	rep(i, n) scanf("%d", &w[i]);

	rep(i, n - 1) {
		int a, b; scanf("%d %d", &a, &b);
		edge[a].push_back(b), edge[b].push_back(a);
	}

	dfs1(), dfs2();
	rep(i, n) root[i] = build(nw[i], 1, 1, n, root[i - 1]);

	rep(i, m) {
		int a, b, c; scanf("%d %d %d", &a, &b, &c);
		res[i] = '0' + ask_route(a, b, c);
	}
	res[m + 1] = 0;
	puts(res + 1);

	return 0;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值