【SCOI2018】Tree(LCT)

描述

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

第一种操作格式为“1 U” ,表示询问从 U 出发的简单路径,经过的点权值 之和的最大值;

第二种操作格式为“2 U V” ,表示将 U 的权值修改为 V。

输入

第一行两个整数 N 和 M,表示树的大小和操作数;

第二行 N-1 个整数,第 i 个整数 Pi(1<=Pi<=i)表示第 i+1 个点与 Pi 有边相 连;

第三行 N 个整数,第 i 个整数 Ai 表示第 i 个点的点权;

接下来 M 行,每行为一个询问操作“1 U” 或修改操作“2 U V” ,按操作发 生的先后顺序给出。

输出

对于每个询问操作,输出一个整数,即经过的点权值之和的最大值

样例输入

6 7
1 1 1 3 3
-1 2 -3 4 -5 6
1 2
1 5
1 6
2 4 5
1 2
1 5
1 6

样例输出

5 
-2
6 
6
-2
7

提示

对于 10%的数据,满足:

1<=N<=1000,1<=M<=1000

对于另外 20%的数据,不存在修改操作

对于另外 20%的数据,满足 Pi=i

对于 100%的数据,满足:

1<=N<=100000, 1<=M<=100000

1<=Pi<=i, -10000<=Ai<=10000

1<=U<=n, -10000<=V<=10000

时限4s,空间限制64MB


解析:

实际上按照SCOI的尿性,这道题时间限制没出成1s都已经算是很仁慈了。

但是64MB的空间限制卡点分树直接把一大波人送退役。

于是有神仙提出了什么这道题的DDP做法。

还有什么两个log的链分治。

实际上如果你做完过SPOJ-QTREE系列,这道题的LCT做法相当SB。(虽然也是两个log)

由于我们需要维护子树信息且全局没有Link和cut操作,所以我们放弃makeroot。

每个点开multiset维护向所有虚子树叉出去的最大路径权值和。

实链上维护链顶和链底在当前链上以及当前链上所有点子树中的答案。

显然就可以轻易在access和pushup的时候维护了。

对于询问,直接将询问的点access变成链底,然后回答当前链底的答案就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const
#define int ll

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		re bool f=0;
		while(!isdigit(c=gc()))f|=c=='-';re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
}
using namespace IO;

using std::cout;
using std::cerr;

cs int N=1e5+1000;
int n,m;

int fa[N],son[N][2];
int sum[N],val[N],lmx[N],rmx[N];
std::multiset<int> s[N];
inline int fir(cs std::multiset<int> &s){return (!s.size())?-0x3f3f3f3f:*--s.end();}

inline void pushup(int u){
	sum[u]=val[u]+sum[son[u][0]]+sum[son[u][1]];
	lmx[u]=std::max(lmx[son[u][0]],sum[son[u][0]]+val[u]+std::max(std::max(0ll,lmx[son[u][1]]),fir(s[u])));
	rmx[u]=std::max(rmx[son[u][1]],sum[son[u][1]]+val[u]+std::max(std::max(0ll,rmx[son[u][0]]),fir(s[u])));
}

inline bool isroot(int u){return son[fa[u]][0]!=u&&son[fa[u]][1]!=u;}
inline bool which(int u){return son[fa[u]][1]==u;}

inline void Rotate(int u){
	int Fa=fa[u],FA=fa[Fa];
	bool pos=which(u);
	if(!isroot(Fa))son[FA][which(Fa)]=u;
	son[Fa][pos]=son[u][!pos];
	if(son[u][!pos])fa[son[Fa][pos]]=Fa;
	son[u][!pos]=Fa;
	fa[Fa]=u,fa[u]=FA;
	pushup(Fa),pushup(u);
}

inline void Splay(int u){
	for(int re Fa=fa[u];!isroot(u);Rotate(u),Fa=fa[u])
	if(!isroot(Fa))Rotate(which(Fa)==which(u)?Fa:u);
}

inline void access(int u){
	for(int re ch=0;u;u=fa[ch=u]){
		Splay(u);
		if(son[u][1])s[u].insert(lmx[son[u][1]]);
		if(ch){
			std::multiset<int>::iterator iter=s[u].find(lmx[ch]);
			if(iter!=s[u].end())s[u].erase(iter);
		}
		son[u][1]=ch;
		pushup(u);
	}
}

signed main(){
	lmx[0]=rmx[0]=-0x3f3f3f3f;
	n=getint(),m=getint();
	for(int re i=2;i<=n;++i)fa[i]=getint();
	for(int re i=1;i<=n;++i)val[i]=getint();
	for(int re i=n;i;--i)pushup(i),s[fa[i]].insert(lmx[i]);
	while(m--)switch(getint()){
		case 1:{
			int u=getint();
			access(u);
			Splay(u);
			cout<<rmx[u]<<"\n";
			break;
		}
		case 2:{
			int u=getint();
			access(u);
			Splay(u);
			val[u]=getint();
			pushup(u);
			break;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值