HDU6962 I love tree (树链剖分)

题目链接: I love tree

大致题意

给定一棵有 n n n个节点的树, 初始树中的各个节点权值为 0 0 0. 有 m m m次如下操作:

1 a b ( a , b ) (a, b) (a,b)路径加上一个平方数列 1 , 4 , 9 , . . . 1, 4, 9, ... 1,4,9,...

2 x询问编号为 x x x的点的权值.

解题思路

树链剖分

我们首先考虑对于一个序列加平方数列的操作.

我们发现就是 ➡️这个题⬅️ (题目的弱化版, 如果没做过推荐先做一下)


我们考虑对于树链上的操作: 比较容易想到可以通过树剖把树上问题转化为区间问题.

由于树链上的编号是一段一段的, 并不连续, 因此我们难以通过维护高阶差分的方式来给区间加上等差数列, 于是考虑通过维护二次函数的方式.

不同于序列上, 我们需要额外维护出, 当前这条链上的节点, 对应到序列中应该是第几个位置.

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;
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], top[N], ind;
//  新编号  新值    重链顶 当前用到的编号
void dfs2(int x = 1, int tp = 1) { // x = 树根节点, tp = 树根节点
	id[x] = ++ind, 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);
	}
}


struct node {
	int l, r;
	ll val;
	ll lazy1, lazy2, lazy3; // lazy1*(x^2)  -2*id*lazy2    lazy3
}t[N << 2];
void pushdown(node& op, ll lazy1, ll lazy2, ll lazy3) {
	op.val += 1ll * op.l * op.l * lazy1 + -2ll * op.l * lazy2 + lazy3;
	op.lazy1 += lazy1, op.lazy2 += lazy2, op.lazy3 += lazy3;
}
void pushdown(int x) {
	if (!t[x].lazy1 and !t[x].lazy2 and !t[x].lazy3) return;
	pushdown(t[x << 1], t[x].lazy1, t[x].lazy2, t[x].lazy3);
	pushdown(t[x << 1 | 1], t[x].lazy1, t[x].lazy2, t[x].lazy3);
	t[x].lazy1 = t[x].lazy2 = t[x].lazy3 = 0;
}

void build(int l, int r, int x = 1) {
	t[x] = { l, r, 0, 0, 0, 0 };
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}

void modify(int l, int r, int c, int x = 1) {
	if (l <= t[x].l and r >= t[x].r) {
		pushdown(t[x], 1, c, 1ll * c * c);
		return;
	}
	pushdown(x);
	int mid = t[x].l + t[x].r >> 1;
	if (l <= mid) modify(l, r, c, x << 1);
	if (r > mid) modify(l, r, c, x << 1 | 1);
}

ll ask(int a, int x = 1) {
	if (t[x].l == t[x].r) return t[x].val;
	pushdown(x);
	int mid = t[x].l + t[x].r >> 1;
	return ask(a, x << 1 | (a > mid));
}


int lca(int a, int b) {
	while (top[a] != top[b]) {
		if (dep[top[a]] < dep[top[b]]) swap(a, b);
		a = p[top[a]];
	}
	return id[a] < id[b] ? a : b;
}
void modify_route(int a, int b) {
	int LCA = lca(a, b);
	int l = 1, r = dep[a] + dep[b] - 2 * dep[LCA] + 1;

	while (top[a] != top[b]) {
		if (dep[top[a]] > dep[top[b]]) {
			int qaq = id[top[a]] - l;
			modify(id[top[a]], id[a], qaq);

			l += id[a] - id[top[a]] + 1;
			a = p[top[a]];
		}
		else {
			int qaq = id[b] - id[top[b]] + 1;
			qaq = r - qaq;
			modify(id[top[b]], id[b], qaq);

			r -= id[b] - id[top[b]] + 1;
			b = p[top[b]];
		}
	}

	if (id[a] < id[b]) {
		int qaq = id[a] - l;
		modify(id[a], id[b], qaq);
	}
	else {
		int qaq = id[b] + r;
		modify(id[b], id[a], qaq);
	}
}
int main()
{
	int n; cin >> n;
	rep(i, n - 1) {
		int a, b; scanf("%d %d", &a, &b);
		edge[a].push_back(b), edge[b].push_back(a);
	}

	dfs1(), dfs2();
	build(1, n);

	int m; cin >> m;
	rep(i, m) {
		int tp; scanf("%d", &tp);
		if (tp == 1) {
			int a, b; scanf("%d %d", &a, &b);
			modify_route(a, b);
		}
		else {
			int x; scanf("%d", &x);
			printf("%lld\n", ask(id[x]));
		}
	}

	return 0;
}

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逍遥Fau

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

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

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

打赏作者

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

抵扣说明:

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

余额充值