「SCOI 2018 D1T1」Tree

传送门


problem

在大小为 n n n 的树上,点从 1 1 1 n n n 标号,第 i i i 个点有权值 a i a_i ai,现在需要支持两种操作:

  • 1 U:询问从 U 出发的所有简单路径中,经过的点权值之和的最大值
  • 2 U V:将 U 的权值修改为 V

注意空间限制 64M

数据范围: 1 ≤ n ≤ 1 0 5 1\le n\le10^5 1n105 1 ≤ m ≤ 1 0 5 1\le m\le10^5 1m105,其中 m m m 为操作的数量。


solution

考虑用 LCT 来维护这个东西,主要就是怎么维护虚儿子的信息

对于 splay 上每个点,记 l m x x / r m x x lmx_x/rmx_x lmxx/rmxx 表示点 x x x “ “ 管辖 ” ” 的实链部分,从链顶 / / /链底出发,能经过的最大点权和

然后对每个 x x x 开个 multiset,记一下每个虚儿子的 l m x lmx lmx(只需要知道虚儿子链顶延申的最大距离即可)。

转移就是,考虑从链顶 / / /链底出发,最大距离不经过 x x x,或者 x x x 就止步,或者在实链上穿过 x x x,或者 x x x 穿到轻儿子。具体就看代码细节吧。

询问的时候,发现把 u u uaccess 一下,答案就是 r m x x rmx_x rmxx

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,inf=1e9;
multiset<int>S[N];
int top(int x)  {return S[x].empty()?-inf:*S[x].rbegin();}
namespace LCT{
	int fa[N],val[N],sum[N],son[N][2],lmx[N],rmx[N];
	bool Get(int x)  {return son[fa[x]][1]==x;}
	bool isroot(int x)  {return (!fa[x])||(son[fa[x]][0]!=x&&son[fa[x]][1]!=x);}
	void pushup(int x){
		sum[x]=sum[son[x][0]]+sum[son[x][1]]+val[x];
		lmx[x]=max(lmx[son[x][0]],sum[son[x][0]]+val[x]+max(0,max(top(x),lmx[son[x][1]])));
		rmx[x]=max(rmx[son[x][1]],sum[son[x][1]]+val[x]+max(0,max(top(x),rmx[son[x][0]])));
	}
	void Rotate(int x){
		int y=fa[x],z=fa[y],k=Get(x),l=son[x][k^1];
		if(!isroot(y))  son[z][Get(y)]=x;fa[x]=z;
		son[y][k]=l,fa[l]=(l?y:0),son[x][k^1]=y,fa[y]=x;
		pushup(y),pushup(x);
	}
	void Splay(int x){
		while(!isroot(x)){
			int y=fa[x];
			if(!isroot(y))  Rotate(Get(x)==Get(y)?y:x);
			Rotate(x);
		}
	}
	void Access(int x){
		for(int i=0;x;x=fa[i=x]){
			Splay(x);
			if(son[x][1])  S[x].insert(lmx[son[x][1]]);
			if(i)  S[x].erase(S[x].find(lmx[i]));
			son[x][1]=i,pushup(x);
		}
	}
}
using namespace LCT;
int n,m;
int main(){
	scanf("%d%d",&n,&m),lmx[0]=rmx[0]=-inf;
	for(int i=2;i<=n;++i)  scanf("%d",&fa[i]);
	for(int i=1;i<=n;++i)  scanf("%d",&val[i]);
	for(int i=n;i>=1;--i)  pushup(i),S[fa[i]].insert(lmx[i]);
	int op,u,v;
	while(m--){
		scanf("%d",&op);
		if(op==1)  scanf("%d",&u),Access(u),Splay(u),printf("%d\n",rmx[u]);
		else  scanf("%d%d",&u,&v),Access(u),Splay(u),val[u]=v;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值