轻重链剖分/树链剖分【模板】

>Link

luogu P3384


>Description

如题,已知一棵包含 NN 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

1 x y z,表示将树从 xx 到 yy 结点最短路径上所有节点的值都加上 zz。

2 x y,表示求树从 xx 到 yy 结点最短路径上所有节点的值之和。

3 x z,表示将以 xx 为根节点的子树内所有节点值都加上 zz。

4 x 表示求以 xx 为根节点的子树内所有节点值之和


>解题思路

树剖的模板题
主要是在树上进行线段树操作(树状数组也可以,但是不能懒标记,会更加费时)
(知道了原理其实还挺好打的,有点像 LCA,线段树也是模板

s o n i son_i soni 表示第 i i i点的儿子中,儿子最重(子树size最大)的那一个
i i i s o n i son_i soni 连的边叫做 重边,多条重边连起来形成 重链
t o p i top_i topi 表示第 i i i点所在的重链最上面的那个点(轻儿子则指向自己)

我们两次 d f s dfs dfs预处理,第二次 d f s dfs dfs中按照先重后轻的顺序遍历,对节点重新标号,这样就能保证一条重链上的节点标号是连在一起的,我们就可以进行线段树了!

子树的操作:重新标号,使得一棵以 x x x为根子树,编号范围为 i d x id_x idx ~ i d x + s i z x − 1 id_x+siz_x-1 idx+sizx1


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define LL long long
using namespace std;

struct edge
{
	LL to, nxt;
} e[2 * N];
LL n, m, root, Mod, a[N], aa[N], hcnt, h[N];
LL cnt, t[5 * N], lazy[5 * N];
LL fa[N], dep[N], top[N], son[N], id[N], siz[N];
LL tot;

void dfs1 (LL now, LL fath)
{
	fa[now] = fath;
	dep[now] = dep[fath] + 1;
	siz[now] = 1;
	for (LL i = h[now]; i; i = e[i].nxt)
	{
		LL v = e[i].to;
		if (v == fath) continue;
		dfs1 (v, now);
		siz[now] += siz[v];
		if (siz[v] > siz[son[now]])
		  son[now] = v;
	}
}
void dfs2 (LL now, LL topp)
{
	id[now] = ++tot;
	top[now] = topp;
	if (son[now]) dfs2 (son[now], topp);
	for (LL i = h[now]; i; i = e[i].nxt)
	{
		LL v = e[i].to;
		if (v == fa[now] || v == son[now])
		  continue;
		dfs2 (v, v);
	}
}
void build_interval (LL k, LL l, LL r)
{
	if (l == r)
	{
		t[k] = a[l];
		return;
	}
	LL mid = (l + r) / 2;
	build_interval (k * 2, l, mid);
	build_interval (k * 2 + 1, mid + 1, r);
	t[k] = t[k * 2] + t[k * 2 + 1];
}
void down_interval (LL k, LL l, LL r)
{
	if (!lazy[k]) return;
	t[k] = (t[k] + lazy[k] * (r - l + 1) % Mod) % Mod;
	lazy[k * 2] = (lazy[k * 2] + lazy[k]) % Mod;
	lazy[k * 2 + 1] = (lazy[k * 2 + 1] + lazy[k]) % Mod;
	lazy[k] = 0;
}
void add_interval (LL k, LL l, LL r, LL ll, LL rr, LL x)
{
	down_interval (k, l, r);
	if (ll <= l && r <= rr)
	{
		lazy[k] = (lazy[k] + x) % Mod;
		return;
	}
	LL mid = (l + r) / 2;
	if (ll <= mid)
	  add_interval (k * 2, l, mid, ll, rr, x);
	if (mid + 1 <= rr)
	  add_interval (k * 2 + 1, mid + 1, r, ll, rr, x);
	down_interval (k * 2, l, mid);
	down_interval (k * 2 + 1, mid + 1, r);
	t[k] = t[k * 2] + t[k * 2 + 1];
}
void add_tree (LL x, LL y, LL z)
{
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]])
		  swap (x, y);
		add_interval (1, 1, n, id[top[x]], id[x], z);
		x = fa[top[x]];
	}
	if (id[x] > id[y]) swap (x, y);
	add_interval (1, 1, n, id[x], id[y], z);
}
LL ask_interval (LL k, LL l, LL r, LL ll, LL rr)
{
	down_interval (k, l, r);
	if (ll <= l && r <= rr) return t[k];
	LL mid = (l + r) / 2, ret = 0;
	if (ll <= mid)
	  ret = (ret + ask_interval (k * 2, l, mid, ll, rr)) % Mod;
	if (mid + 1 <= rr)
	  ret = (ret + ask_interval (k * 2 + 1, mid + 1, r, ll, rr)) % Mod;
	return ret;
}
LL ask_tree (LL x, LL y)
{
	LL ret = 0;
	while (top[x] != top[y]) //不断往上跳,直到两点处于一条重链上
	{
		if (dep[top[x]] < dep[top[y]])
		  swap (x, y);
		ret = (ret + ask_interval (1, 1, n, id[top[x]], id[x])) % Mod; //将每一条链进行处理
		x = fa[top[x]]; //链处理过了,直接跳到链的父亲那
	}
	if (id[x] > id[y]) swap (x, y);
	ret = (ret + ask_interval (1, 1, n, id[x], id[y])) % Mod;
	return ret;
}

int main()
{
	LL u, v;
	scanf ("%lld%lld%lld%lld", &n, &m, &root, &Mod);
	for (LL i = 1; i <= n; i++)
	  scanf ("%lld", &aa[i]);
	for (LL i = 1; i < n; i++)
	{
		scanf ("%lld%lld", &u, &v);
		e[++hcnt] = (edge){v, h[u]}; h[u] = hcnt;
		e[++hcnt] = (edge){u, h[v]}; h[v] = hcnt;
	}
	dfs1 (root, 0);
	dfs2 (root, root);
	for (LL i = 1; i <= n; i++)
	  a[id[i]] = aa[i];
	build_interval (1, 1, n);
	LL E, x, y, z;
	for (LL i = 1; i <= m; i++)
	{
		scanf ("%lld%lld", &E, &x);
		if (E == 1)
		{
			scanf ("%lld%lld", &y, &z);
			add_tree (x, y, z % Mod);
		}
		else if (E == 2)
		{
			scanf ("%lld", &y);
			printf ("%lld\n", ask_tree (x, y));
		}
		else if (E == 3)
		{
			scanf ("%lld", &z);
			add_interval (1, 1, n, id[x], id[x] + siz[x] - 1, z % Mod);
		}
		else
		  printf ("%lld\n", ask_interval (1, 1, n, id[x], id[x] + siz[x] - 1));
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值