[Luogu P3676] 小清新数据结构题

本文解析了一道洛谷平台上的数据结构题目,涉及树链剖分和子树权值和的计算。通过详细分析,提供了针对不同操作场景的高效算法实现,包括点权更新和子树点权和的平方和查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门

题目描述

在很久很久以前,有一棵 n n n个点的树,每个点有一个点权。

现在有 q q q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方和。

(题目不是很好懂,没看太懂的可以看看样例解释)

输入输出格式

输入格式:

第一行两个整数 n n n q q q

接下来 n − 1 n-1 n1行每行两个整数 a a a b b b,表示树中 a a a b b b之间有一条边,保证给出的边不会重复。

接下来一行 n n n个整数,第i个整数表示第 i i i个点的点权。

接下来 q q q行每行两或三个数,如果第一个数为 1 1 1,那么接下来有两个数 x x x y y y,表示将第 x x x个点的点权修改为 y y y,如果第一个数为 2 2 2,那么接下来有一个数 x x x,表示询问以x为根时每棵子树点权和的平方和。

输出格式:

对于每个询问输出答案。

输入输出样例

输入样例#1:
4 5
1 2
2 3
2 4
4 3 2 1
2 2
1 1 3
2 3
1 2 4
2 4
输出样例#1:
121
140
194

说明

样例解释

这是一个菊花图, 2 2 2 1 1 1 3 3 3 4 4 4间有边。

一开始每个点点权分别为 4 4 4 3 3 3 2 2 2 1 1 1

第一个询问以 2 2 2为根, 1 1 1 3 3 3 4 4 4子树中都只有本身一个点, 2 2 2子树中有所有点,那么 1 1 1 3 3 3 4 4 4子树中的点权和就分别是自己的点权 4 4 4 2 2 2 1 1 1 2 2 2子树中的点权和就是 4 + 3 + 2 + 1 = 10 4+3+2+1=10 4+3+2+1=10 4 2 4^2 42+ 2 2 2^2 22+ 1 2 1^2 12+ 1 0 2 10^2 102= 121 121 121

接下来将第一个点点权修改为 3 3 3,每个点点权分别为 3 3 3 3 3 3 2 2 2 1 1 1

第二个询问以 3 3 3为根, 1 1 1 4 4 4子树中只有自己, 2 2 2子树中有 1 1 1 2 2 2 4 4 4 3 3 3子树中有所有点, 1 1 1 4 4 4子树点权和就是自己的点权 3 3 3 1 1 1 2 2 2子树点权和就是 3 + 3 + 1 = 7 3+3+1=7 3+3+1=7 3 3 3子树点权和为 3 + 3 + 2 + 1 = 9 3+3+2+1=9 3+3+2+1=9 3 2 3^2 32+ 1 2 1^2 12+ 7 2 7^2 72+ 9 2 9^2 92= 140 140 140

接下来把第二个点点权修改为 4 4 4,每个点点权分别为 3 3 3 4 4 4 2 2 2 1 1 1

第三个询问以 4 4 4为根, 1 1 1 3 3 3子树点权和就是 3 3 3 2 2 2 2 2 2子树点权和就是 3 + 4 + 2 = 9 3+4+2=9 3+4+2=9 4 4 4子树点权和为 3 + 4 + 2 + 1 = 10 3+4+2+1=10 3+4+2+1=10 3 2 + 2 2 + 9 2 + 1 0 2 = 194 3^2+2^2+9^2+10^2=194 32+22+92+102=194

数据范围

对于10%的数据, n , q ≤ 2000 n,q \leq 2000 n,q2000

对于40%的数据, n , q ≤ 60000 n,q \leq 60000 n,q60000

对于另外30%的数据,保证每次询问的根都为 1 1 1

对于100%的数据, 1 ≤ n , q ≤ 200000 1 \leq n,q \leq 200000 1n,q200000 − 10 ≤ -10 \leq 10输入的每个点权 ≤ 10 \leq 10 10

建议使用输入优化~~,虽然标程没加读入优化也能过~~

解题分析

真的是一道小清新数据结构题, 并没有什么毒瘤的数据结构套数据结构的情况。

考虑 30 p t s 30pts 30pts的情况, 我们每次根都在 1 1 1号点, 也只需要维护每次修改后答案即可。 考虑修改的位置为 u u u, 实际有影响的子树权值和只有处于 u u u点和 u u u上方的点, 设处于 1 → u 1\to u 1u号点路径上的点依次是 1 , 2 , 3 , . . . , u 1,2,3,...,u 1,2,3,...,u, 修改的 Δ \Delta Δ值为 d e l del del, 那么现在的答案就是。
∑ i = 1 u ( s u m [ i ] + d e l ) 2 − ∑ i = 1 u s u m [ i ] 2 = u × d e l 2 + 2 × ( ∑ i = 1 u ) × s u m [ i ] × d e l \sum_{i=1}^{u}(sum[i]+del)^2-\sum_{i=1}^{u}sum[i]^2 \\ =u\times del^2+2\times (\sum_{i=1}^u)\times sum[i]\times del i=1u(sum[i]+del)2i=1usum[i]2=u×del2+2×(i=1u)×sum[i]×del
于是我们预处理出每个点的子树和, 然后链上查询和就好了。

我们再考虑如果根不是 1 1 1而是 v v v的情况, 实质上对答案有影响的也只有 1 → v 1\to v 1v这条链上的点, 设 s [ i ] s[i] s[i]为以 v v v为根的子树权值和, 1 → u 1\to u 1u号点路径上经过的点分别是 1 , 2 , 3 , . . . , 1,2,3,..., 1,2,3,...,, 可以发v现 s u m [ i + 1 ] + s [ i ] = ∑ v a l [ i ] sum[i+1]+s[i]=\sum_{val[i]} sum[i+1]+s[i]=val[i], 而 s [ u ] = s u m [ 1 ] s[u]=sum[1] s[u]=sum[1], 设 t o t = ∑ v a l [ i ] tot=\sum_{val[i]} tot=val[i]那么现在的答案就是:
a n s ′ = a n s + ∑ i = 1 v − 1 s [ i ] − ∑ i = 2 v s u m [ i ] = a n s + ∑ i = 2 v ( t o t − s u m [ i ] ) 2 − ∑ i = 2 v s u m [ i ] 2 = a n s + ( v − 1 ) × t o t 2 − 2 × t o t × ∑ i = 2 v s u m [ i ] = a n s + ( v + 1 ) × t o t 2 − 2 × t o t × ∑ i = 1 v s u m [ i ] ans'=ans+\sum_{i=1}^{v-1}s[i]-\sum_{i=2}^{v}sum[i] \\ =ans+\sum_{i=2}^{v}(tot-sum[i])^2-\sum_{i=2}^{v}sum[i]^2 \\ =ans+(v-1)\times tot^2-2\times tot\times \sum_{i=2}^{v} sum[i] \\ =ans+(v+1)\times tot^2-2\times tot\times \sum_{i=1}^{v}sum[i] ans=ans+i=1v1s[i]i=2vsum[i]=ans+i=2v(totsum[i])2i=2vsum[i]2=ans+(v1)×tot22×tot×i=2vsum[i]=ans+(v+1)×tot22×tot×i=1vsum[i]
同样一个链上和就好了。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 200500
#define ls tree[now].son[0]
#define rs tree[now].son[1]
#define dad tree[now].fat
bool neg;
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc)
	if(c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if(neg) x = -x, neg = false;
}
int sta[MX], head[MX], val[MX];
int dot, q, cnt, top;
ll ans;
struct Edge {int to, nex;} edge[MX << 1];
struct Node {int son[2], fat, val, siz, tag; ll sub, sum; bool rev;} tree[MX];
IN void add(R int from, R int to) {edge[++cnt] = {to, head[from]}, head[from] = cnt;}
void DFS (R int now, R int fa)
{
	tree[now].sum = tree[now].val;
	for (R int i = head[now]; i; i = edge[i].nex)
	{
		if(edge[i].to ^ fa)
		{
			tree[edge[i].to].fat = now, DFS(edge[i].to, now);
			tree[now].sum += tree[edge[i].to].sum;
		}
	}
	ans += tree[now].sum * tree[now].sum;
	tree[now].sub = tree[now].sum;
}
namespace LCT
{
	IN bool get(R int now) {return tree[dad].son[1] == now;}
	IN bool nroot(R int now) {return tree[dad].son[1] == now || tree[dad].son[0] == now;}
	IN void pushrev(R int now) {std::swap(ls, rs), tree[now].rev ^= 1;}
	IN void pushdown(R int now)
	{
		if(tree[now].rev) pushrev(ls), pushrev(rs), tree[now].rev = 0;
		if(tree[now].tag)
		{
			if(ls) tree[ls].sum += tree[ls].siz * tree[now].tag, tree[ls].tag += tree[now].tag, tree[ls].sub += tree[now].tag;
			if(rs) tree[rs].sum += tree[rs].siz * tree[now].tag, tree[rs].tag += tree[now].tag, tree[rs].sub += tree[now].tag;
			tree[now].tag = 0;
		}
	}
	IN void pushup(R int now)
	{
		tree[now].siz = tree[ls].siz + tree[rs].siz + 1;
		tree[now].sum = tree[ls].sum + tree[rs].sum + tree[now].sub;
	}
	IN void rotate(R int now)
	{
		R int fa = dad, grand = tree[fa].fat;
		R bool dir = get(now);
		tree[fa].son[dir] = tree[now].son[dir ^ 1];
		tree[tree[now].son[dir ^ 1]].fat = fa;
		tree[now].fat = grand;
		if(nroot(fa)) tree[grand].son[get(fa)] = now;
		tree[now].son[dir ^ 1] = fa;
		tree[fa].fat = now;
		pushup(fa);
	}
	IN void splay(R int now)
	{
		int tmp, fa;
		sta[top = 1] = tmp = now;
		W (nroot(now)) sta[++top] = now = dad;
		W (top) pushdown(sta[top--]); now = tmp;
		W (nroot(now))
		{
			fa = dad;
			if(nroot(fa)) rotate(get(now) == get(fa) ? fa : now);
			rotate(now);
		}
		pushup(now);
	}
	IN void access(R int now)
	{
		for(R int x = 0; now; x = now, now = dad)
		splay(now), rs = x, pushup(now);
	}
	IN void makeroot(R int now) {access(now), splay(now), pushrev(now);}
	IN void split(R int x, R int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	IN void modify(R int pos, R int del)
	{
		split(pos, 1); tree[1].sub += del;
		ans += del * del * tree[1].siz + 2 * del * tree[1].sum;
		tree[1].sum += tree[1].siz * del;
		tree[1].tag += del;
	}
	IN void query(R int pos)
	{
		split(pos, 1);
		printf("%lld\n", ans + (tree[1].siz + 1) * tree[1].sub * tree[1].sub - 2 * tree[1].sub * tree[1].sum);
	}
}
int main(void)
{
	int a, b, opt;
	in(dot), in(q);
	for (R int i = 1; i < dot; ++i) in(a), in(b), add(a, b), add(b, a);
	for (R int i = 1; i <= dot; ++i)
	in(tree[i].val), val[i] = tree[i].val;
	DFS(1, 0);
	W (q--)
	{
		in(opt), in(a);
		if(opt & 1) in(b), LCT::modify(a, b - val[a]), val[a] = b;
		else LCT::query(a);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值