树的统计 LibreOJ - 10138

https://loj.ac/p/10138

题目大意:有一棵n个点的树,现要询问某一区间的和或最小值,并改变某一点的权值

思路:(树链剖分)将树上的点以dfs序建立线段树,之后进行线段树的操作,操作的左端点和右端点为dfs序的点的编号

#include<__msvc_all_public_headers.hpp>
using namespace std;
const int N = 3e4 + 5;
struct Edge
{
	int v, next;
}edge[N << 1];//链式前向星存图
int head[N], a[N], dfn[N];
int cnt = 0;
struct Tree 
{
	int l, r, sum, max;//线段树
}tree[N << 2];
void addedge(int u, int v)
{
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
int son[N], siz[N], dep[N], fa[N];
void dfs1(int u)
{
	son[u] = -1;//重子节点
	siz[u] = 1;//子节点大小
	for (int i = head[u]; i; i = edge[i].next)
	{
		int v = edge[i].v;
		if (dep[v])//防止重复访问
			continue;
		dep[v] = dep[u] + 1;//当前深度
		fa[v] = u;//父结点信息
		dfs1(v);
		siz[u] += siz[v];
		if (son[u] == -1 || siz[v] > siz[son[u]])//取子节点中子树最大的点为重节点
			son[u] = v;
	}
}
int top[N], tot = 0, rnk[N];
void dfs2(int u, int t)
{
	top[u] = t;//所在链的链顶
	dfn[u] = ++tot;//dfs序
	rnk[tot] = u;//dfs序编号
	if (son[u] == -1)
		return;
	dfs2(son[u], t);
	for (int i = head[u]; i; i = edge[i].next)
	{
		int v = edge[i].v;
		if (dfn[v])
			continue;
		dfs2(v, v);
	}
}
void cal(int rt)
{//将子节点信息传给父结点
	tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
	tree[rt].max = max(tree[rt << 1].max, tree[rt << 1 | 1].max);
}
void build(int l, int r, int rt)
{//建立线段树
	tree[rt].l = l;
	tree[rt].r = r;
	if (l == r)
	{
		tree[rt].max = tree[rt].sum = a[rnk[l]];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, rt << 1);
	build(mid + 1, r, rt << 1 | 1);
	cal(rt);
}
void change(int pos, int d, int num)
{//单点修改
	if (tree[num].l == tree[num].r)
	{//找到了最底层的节点
		tree[num].max = tree[num].sum = d;
		return;
	}
	int mid = (tree[num].l + tree[num].r >> 1);
	if (pos <= mid)
	{
		change(pos, d, num << 1);
	}
	else
	{
		change(pos, d, num << 1 | 1);
	}
	cal(num);
}
int calsum(int l, int r, int num)
{//线段树求和
	if (l <= tree[num].l && r >= tree[num].r)
	{
		return tree[num].sum;
	}
	int mid = (tree[num].l + tree[num].r) >> 1;
	int val = 0;
	if (l <= mid)
	{
		val += calsum(l, r, num << 1);
	}
	if (r > mid)
	{
		val += calsum(l, r, num << 1 | 1);
	}
	return val;
}
int calmax(int l, int r, int num)
{//线段树查询最值
	if (l <= tree[num].l && r >= tree[num].r)
	{
		return tree[num].max;
	}
	int mid = (tree[num].l + tree[num].r) >> 1;
	int val = -(1 << 30);
	if (l <= mid)
	{
		val = max(val, calmax(l, r, num << 1));
	}
	if (r > mid)
	{
		val = max(val, calmax(l, r, num << 1 | 1));
	}
	return val;
}
int querysum(int x, int y)
{
	int ret = 0, fx = top[x], fy = top[y];
	while (fx != fy)
	{
		if (dep[fx] >= dep[fy])
		{
			ret += calsum(dfn[fx], dfn[x], 1);
			x = fa[fx];
			fx = top[x];
		}
		else
		{
			ret += calsum(dfn[fy], dfn[y], 1);
			y = fa[fy];
			fy = top[y];
		}
	}
	if (dfn[x] < dfn[y])
	{
		ret += calsum(dfn[x], dfn[y], 1);
	}
	else
	{
		ret += calsum(dfn[y], dfn[x], 1);
	}
	return ret;
}
int querymax(int x, int y)
{
	int ret = -(1 << 30), fx = top[x], fy = top[y];
	while (fx != fy)//直到链的端点相遇
	{
		if (dep[fx] >= dep[fy])//确保区间左端点小于右端点
		{
			ret = max(ret, calmax(dfn[fx], dfn[x], 1));
			x = fa[fx];
			fx = top[x];//将链的顶点向上移,维护线段树
		}
		else
		{
			ret = max(ret, calmax(dfn[fy], dfn[y], 1));
			y = fa[fy];
			fy = top[y];
		}
	}
	if (dfn[x] < dfn[y])//确保区间左端点小于右端点
	{
		ret = max(ret, calmax(dfn[x], dfn[y], 1));
	}
	else
	{
		ret = max(ret, calmax(dfn[y], dfn[x], 1));
	}
	return ret;
}
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf_s("%d%d", &u, &v);
		addedge(u, v);
		addedge(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		scanf_s("%d", &a[i]);
	}
	dep[1] = 1;
	dfs1(1);
	dfs2(1, 1);
	build(1, n, 1);
	int q;
	cin >> q;
	while (q--)
	{
		char op[10];
		int l, r;
		cin >> op;
		scanf_s("%d%d", &l, &r);
		if (op[0] == 'C')
		{
			change(dfn[l], r, 1);
		}
		else if (op[1] == 'M')
		{
			printf("%d\n", querymax(l, r));
		}
		else
		{
			printf("%d\n", querysum(l, r));
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

timidcatt

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

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

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

打赏作者

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

抵扣说明:

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

余额充值