【树链剖分】【模板】树的统计(P2590)

这篇博客详细介绍了如何利用树链剖分和线段树来解决树上单点修改和区间查询的问题。首先,通过深度优先搜索(DFS)确定树的深度、节点的父亲节点以及重儿子,接着再次DFS得到DFS序和重链顶点。然后,博主解释了如何在树的重链上应用线段树进行区间操作,包括查询最大值和求和。最后,给出了完整的C++代码实现,并展示了样例输入和输出。
摘要由CSDN通过智能技术生成

链接

Luogu P2590

题目描述

给出一棵树,对该树进行单点修改,区间查询最大值,区间求和

样例输入

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5	
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

样例输出

4
1
2
2
10
6	
5
6
5
16

思路

路见不平一声吼啊,该出手时就出手啊,风风火火闯九州啊 (突然唱起
显然,树链剖分(以下皆为该题玩法,请勿当真)


树链剖分嘛,就是在树上进行区间操作
看见区间操作,不自然地想到线段树
树链剖分,不严谨地说,就是在树上跑线段树(错误说法,仅供搞笑,莫当真)
怎么实现勒
我们想想,不知道为什么地,我们要记录重儿子(子树大小最大的儿子(据说是为了压时间复杂度)
然后可以根据重儿子构成的链(简称重链),就可以让dfs序连起来,那么就可以实现连续一段区间,就可以丢进线段树里了
那么查询,每次从链顶深度较大的出发,向上跳,同时求出区间的信息(比如最大值,和等),然后一直跑到两点在一条链上的时候,再处理两点间的区间就可以了
具体步骤
1.dfs求出深度,父亲,重儿子
2.再次dfs求出dfs序,重链的顶点
3.树上跳点
4.跳的过程中在线段树里进行求值


码量很大,谨慎食用

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long

using namespace std;

int t, n, tot, q;
int id[300005], h[300005];
ll w[300005], a[300005];
int size[300005], f[300005], dep[300005], son[300005], top[300005];

struct tr
{
	int to, next;
}g[1000005];

struct lq
{
	ll x;
	ll maxn = -1e17;
}tree[2000005];

int read()
{
	ll re = 0, pd = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') pd = -1;//记得判负
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		re = re * 10 + c - 48;
		c = getchar();
	}
	return re * pd;
}

void add(int x, int y)
{
	g[++t] = (tr){y, h[x]}; h[x] = t;
	g[++t] = (tr){x, h[y]}; h[y] = t;
}

void dfs1(int x, int fath)
{
	size[x] = 1;
	f[x] = fath;
	for(int i = h[x]; i; i = g[i].next)
	{
		int to = g[i].to;
		if(to == fath) continue;
		dep[to] = dep[x] + 1;
		dfs1(to, x);
		size[x] += size[to];
		if(size[to] > size[son[x]]) son[x] = to;
	}
}//第一遍dfs

void dfs2(int x, int t)//t是该重链的链顶
{
	id[x] = ++tot;
	a[tot] = w[x];
	top[x] = t;
	if(son[x]) dfs2(son[x], t);
	for(int i = h[x]; i; i = g[i].next)
	{
		int to = g[i].to;
		if(g[i].to == f[x] || to == son[x]) continue;
		dfs2(to, to);//不是该点的重儿子那当然不在该点的重链上啦
	}
}//第二遍dfs

ll ask(int x, int l, int r, int go)
{
	if(l == r)
		return tree[x].x;
	int mid = (l + r) / 2;
	if(go <= mid) return ask(x * 2, l, mid, go);
	if(go > mid) return ask(x * 2 + 1, mid + 1, r, go);
}

void change(int x, int l, int r, int go, ll z)
{
	if(l == r) {
		tree[x].x += z;
		tree[x].maxn = tree[x].x;
		return;
	}
	int mid = (l + r) / 2;
	if(go <= mid) change(x * 2, l, mid, go, z);
	if(go > mid) change(x * 2 + 1, mid + 1, r, go, z);
	tree[x].x = tree[x * 2].x + tree[x * 2 + 1].x;
	tree[x].maxn = max(tree[x * 2].maxn, tree[x * 2 + 1].maxn);
}

ll ask_max(int x, int l, int r, int L, int R)
{
	if(L <= l && r <= R) return tree[x].maxn;
	int mid = (l + r) / 2;
	ll p = -1e18;
	if(L <= mid) p = max(p, ask_max(x * 2, l, mid, L, R));
	if(R > mid) p = max(p, ask_max(x * 2 + 1, mid + 1, r, L, R));
	return p;
}

ll mmax(int x, int y)
{
	ll ans = -1e18;
	int fx = top[x], fy = top[y];
	while(fx != fy)
	{
		if(dep[fx] < dep[fy]) swap(x, y), swap(fx, fy);
		ans = max(ans, ask_max(1, 1, n, id[fx], id[x]));
		x = f[fx]; fx = top[x];
	}
	if(id[x] > id[y]) swap(x, y);
	ans = max(ans, ask_max(1, 1, n, id[x], id[y]));
	return ans;
}

ll ask_sum(int x, int l, int r, int L, int R)
{
	if(L <= l && r <= R) return tree[x].x;
	int mid = (l + r) / 2;
	ll re = 0;
	if(L <= mid) re += ask_sum(x * 2, l, mid, L, R);
	if(R > mid) re += ask_sum(x * 2 + 1, mid + 1, r, L, R);
	return re;
}

ll ssum(int x, int y)
{
	ll ans = 0;
	int fx = top[x], fy = top[y];
	while(fx != fy)
	{
		if(dep[fx] < dep[fy]) swap(x, y), swap(fx, fy);
		ans += ask_sum(1, 1, n, id[fx], id[x]);
		x = f[fx]; fx = top[x];
	}
	if(id[x] > id[y]) swap(x, y);
	ans += ask_sum(1, 1, n, id[x], id[y]);
	return ans;
}

void build(int x, int l, int r, int go)
{
	if(l == r)
	{
		tree[x].x = a[go];
		tree[x].maxn = a[go];
		return;
	}
	int mid = (l + r) / 2;
	if(go <= mid) build(x * 2, l, mid, go);
	if(mid < go) build(x * 2 + 1, mid + 1, r, go);
	tree[x].x = tree[x * 2].x + tree[x * 2 + 1].x;
	tree[x].maxn = max(tree[x * 2].maxn, tree[x * 2 + 1].maxn);
}

void write(ll x)
{
    if (x < 0) { 
		x = -x; 
		putchar('-');
	}
	if (x > 9) write(x / 10);
	putchar(x % 10 + 48);
	return;
}

int main()
{
//	freopen("data1.in","r",stdin); 
//	freopen("1.txt","w",stdout);
	n = read();
	for(int i = 1; i < n; ++i)
	{
		int u, v;
		u = read();
		v = read();
		add(u, v);
	}
	dfs1(1, 0);
	for(int i = 1; i <= n; ++i)
		w[i] = read();
	dfs2(1, 1);
	for(int i = 1; i <= n; ++i)
		build(1, 1, n, id[i]);
	q = read();
	char s[5050];
	int x, y;
	for(int i = 1; i <= q; ++i)
	{
		scanf("%s", s + 1);
		x = read(); y = read();
		if(x == 4613 && y == 8781) 
			bool qqq = 1;
		if(s[1] == 'Q') {
			if(s[2] == 'M') 
				write(mmax(x, y)), putchar('\n');
			else 
				write(ssum(x, y)), putchar('\n');
		}
		else {
			ll k = ask(1, 1, n, id[x]);//先求出该点的值
			change(1, 1, n, id[x], (ll)y - k);//再进行区间修改
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值