数据结构·树上信息维护·题解【树上修改】


这个寒假过得……不是用充实二字就能简简单单形容得了的啊!但不论如何,寒假毕竟是寒假。所以既然是最后一天,那么就笑着迎接它的终结吧。
今天要来总结一下树上路径修改,单点查询的操作。

题目

WOJ#2230 树上修改
WOJ题目链接

题目描述

有n个节点N-1条边,这是一颗树,有2个操作:
1 x y v:表示将节点x到y最短路径上所有的点的权值+v
2 x:表示查询节点x的权值
开始的时候每个节点的权值是0

输入

第一行是数N,表示N个节点 接下来n-1行,每行描述了n-1条边。
接下来是一个数q表示有q次查询与询问 接下来q行,格式如题。

输出

若干行(官方省略)

样例

  • 输入样例
    3
    1 2
    2 3
    3
    1 1 2 5
    1 1 3 2
    2 2
  • 输出样例
    7

说明

n<=1e5 q<=1e5

题意

嘛,大概就是,路径修改,单点查询。

思路

首先,考虑树上两点间的路径修改。依次修改显然是不现实的,这时候就考虑差分。
x x x点到 y y y点的路径上 + v +v +v,就可以转化为由 x x x r o o t root root的路径 + v +v +v,再由 y y y r o o t root root的路径 + v +v +v,为了处理掉 r o o t root root以上的部分,再由 l c a ( x , y ) lca(x,y) lca(x,y) r o o t root root的路径 − v -v v,可这时 l c a ( x , y ) lca(x,y) lca(x,y)就被清零了,所以再加上一个 v v v
其次,考虑如果 x x x的修改对 y y y有贡献,只有 x x x y y y的子树中才会产生。于是,可以将这个问题转化为:修改一个点的值,查询一个子树的权值和。

代码

#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
const int maxm=2e5+10;
int n,q,tim,cnt=2;
int c[maxn],first[maxm],st[maxn],ed[maxn],dep[maxn],apple[maxn];
int fa[maxn][20];
bool vis[maxn];
struct edge {int u,v,nxt;};
edge e[maxm];
inline int read()
{
	int x=0;bool f=0;char c=getchar();
	while(!isdigit(c)) {f|=c=='-';c=getchar();}
	while(isdigit(c)) {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return f?-x:x;
}
inline void add(int u,int v)
{
	e[cnt]=(edge){u,v,first[u]};
	first[u]=cnt++;
}
inline void dfs(int x)
{
	tim++;
	st[x]=tim;
	vis[x]=1;
	for(int i=1;(1<<i)<=dep[x];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=first[x];i;i=e[i].nxt)
		if(!vis[e[i].v])
		{
			dep[e[i].v]=dep[x]+1;
			fa[e[i].v][0]=x;
			dfs(e[i].v);
		}
	ed[x]=tim;
}
inline int lowbit(int x)
{
	return x&(-x);
}
inline void update(int x,int y)
{
	while(x<=n) {c[x]+=y;x+=lowbit(x);}
}
inline int query(int x)
{
	int ret=0;
	while(x>0)
	{
		ret+=c[x];
		x-=lowbit(x);
	}
	return ret;
}
int lca(int a,int b)
{
	if(dep[a]<dep[b]) swap(a,b);
	int t=dep[a]-dep[b];
	for(int i=0;(1<<i)<=t;i++)
		if(t&(1<<i)) a=fa[a][i];
	if(a==b) return a;
	for(int j=19;j>=0;j--)
		if(fa[a][j]!=fa[b][j])
			a=fa[a][j],b=fa[b][j];
	return fa[a][0];
}
int main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		add(u,v);
		add(v,u);
	}
	dfs(1);
	q=read();
	for(int i=1;i<=q;i++)
	{
		int opt=read(),l,r,v,x;
		switch (opt)
		{
			case 1:
			{
				l=read(),r=read(),v=read();
				int f=lca(l,r);
				update(st[l],v);
				update(st[r],v);
				update(st[f],(-2)*v);
				apple[f]+=v;
				break;
			}
			case 2:
			{
				x=read();
				cout<<query(ed[x])-query(st[x]-1)+apple[x]<<"\n";
				break;
			}
		}
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值