2018.10.19【NOIP练习】树链剖分(换根树剖)

描述

给定一棵 n 个节点的树,初始时该树的根为 1 号节点,每个节点有一个给定的权值。下面依次进行 m 个操作,操作分为如下五种类型:

换根:将一个指定的节点设置为树的新根。

修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。

修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。

询问路径:询问某条路径上节点的权值和。

询问子树:询问某个子树内节点的权值和。

输入

第一行为一个整数 n,表示节点的个数。

第二行 n 个整数表示第$ i $个节点的初始权值 a i a_i ai

第三行 n − 1 n−1 n1 个整数,表示 i + 1 i+1 i+1 号节点的父节点编号 f i + 1   ( 1 ⩽ f i + 1 ⩽ n ) f_{i+1}\ (1 \leqslant f_{i+1} \leqslant n) fi+1 (1fi+1n)

第四行一个整数 m m m,表示操作个数。

接下来 m m m 行,每行第一个整数表示操作类型编号: ( 1 ⩽ u , v ⩽ n ) (1 \leqslant u, v \leqslant n) (1u,vn)

若类型为 1 1 1,则接下来一个整数 u u u,表示新根的编号。

若类型为 2 2 2,则接下来三个整数 u , v , k u,v,k u,v,k,分别表示路径两端的节点编号以及增加的权值。

若类型为 3 3 3,则接下来两个整数 u , k u,k u,k,分别表示子树根节点编号以及增加的权值。

若类型为 4 4 4,则接下来两个整数 u , v u,v u,v,表示路径两端的节点编号。

若类型为 5 5 5,则接下来一个整数 u u u,表示子树根节点编号。

输出

对于每一个类型为 4 或 5 的操作,输出一行一个整数表示答案。

样例输入

6
1 2 3 4 5 6
1 2 1 4 4
6
4 5 6
2 2 4 1
5 1
1 4
3 1 2
4 2 5

样例输出

15
24
19

提示

对于 100 % 100\% 100% 的数据, 1 ⩽ n , m , k , a i ⩽ 1 0 5 1\leqslant n,m,k,a_i\leqslant 10^5 1n,m,k,ai105。数据有一定梯度。


解析:

其实说是换根,并不会真正的换根,只是换一个根标记的位置,不然怎么维护原来轻重链剖分的优秀结构。

思路:

改变根对路径的形态显然没有影响。

考虑怎么计算出根改变后不同节点的当前子树。

考虑我们现在的树根是 r o o t root root,当前询问的子树根节点是 u u u
那么会有以下情况(以下 L C A LCA LCA均指原树中的 L C A LCA LCA
1. u = = r o o t u==root u==root,那么 u u u的子树就是整棵树。
2. L C A ( r o o t , u ) ≠ u LCA(root,u)\neq u LCA(root,u)̸=u,即 r o o t root root不在 u u u的子树中。那么 u u u现在的子树就是原来的子树
3. L C A ( r o o t , u ) = u LCA(root,u)=u LCA(root,u)=u,即 u u u在原来的树中是 r o o t root root的祖先。那么我们找到 u u u r o o t root root路径上的第一个儿子。这个儿子对应的原树中的子树,就是现在 u u u的子树的补集。

分别对应转换一下就好了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline int getint(){
	re int num;
	re char c;
	re bool f=0;
	while(!isdigit(c=gc()))if(c=='-')f=1;num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return f?-num:num;
}

inline void outint(ll a){
	static char ch[23];
	if(a==0)pc('0');
	if(a<0)pc('-'),a=-a;
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

cs int N=100005;
int last[N],nxt[N<<1],to[N<<1],ecnt;
inline void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int son[N],siz[N],fa[N],top[N],dep[N],val[N];
void dfs1(int u){
	siz[u]=1;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}

int in[N],out[N],tot,pos[N];
void dfs2(int u){
	pos[in[u]=++tot]=u;
	if(son[u]){
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u]||v==son[u])continue;
		top[v]=v;
		dfs2(v);
	}
	out[u]=tot;
}

inline int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=fa[top[v]];
	}
	return dep[u]>dep[v]?v:u;
}

int root;
inline void tree_dissection(){
	dfs1(root=1);
	top[1]=1;
	dfs2(1);
}

ll sum[N<<2],add[N<<2];

inline void pushup(int k){
	sum[k]=sum[k<<1]+sum[k<<1|1];
}

inline void pushnow(int k,int l,int r,int val){
	add[k]+=val;
	sum[k]+=1ll*(r-l+1)*val;
}

inline void pushdown(int k,int l,int r){
	if(add[k]){
		int mid=(l+r)>>1;
		pushnow(k<<1,l,mid,add[k]);
		pushnow(k<<1|1,mid+1,r,add[k]);
		add[k]=0;
	}
}

void build(int k,int l,int r){
	if(l==r){
		sum[k]=val[pos[l]];
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}

ll query(int k,int l,int r,cs int &ql, cs int &qr){
	if(ql<=l&&r<=qr)return sum[k];
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	if(qr<=mid)return query(k<<1,l,mid,ql,qr);
	if(mid<ql)return query(k<<1|1,mid+1,r,ql,qr);
	return query(k<<1,l,mid,ql,qr)+query(k<<1|1,mid+1,r,ql,qr);
}

void modify(int k,int l,int r,cs int &ql,cs int &qr,cs int &val){
	if(ql<=l&&r<=qr)return pushnow(k,l,r,val);
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid)modify(k<<1,l,mid,ql,qr,val);
	if(qr>mid)modify(k<<1|1,mid+1,r,ql,qr,val);
	pushup(k);
}
int n,m;

inline void addpath(int u,int v,int val){
	while(top[u]!=top[v]){
		if(dep[top[v]]>dep[top[u]])swap(u,v);
		modify(1,1,n,in[top[u]],in[u],val);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	modify(1,1,n,in[v],in[u],val);
}

inline ll querypath(int u,int v){
	ll res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res+=query(1,1,n,in[top[u]],in[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	return res+query(1,1,n,in[v],in[u]);
}

inline int find(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		if(fa[top[u]]==v)return top[u];
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	return son[v];
}

inline void addsubtree(int u,int val){
	if(u==root)return modify(1,1,n,1,n,val);
	int lca=LCA(u,root);
	if(lca!=u)return modify(1,1,n,in[u],out[u],val);
	int son=find(u,root);
	modify(1,1,n,1,n,val);
	modify(1,1,n,in[son],out[son],-val);
}

inline ll querysubtree(int u){
	if(u==root)return query(1,1,n,1,n);
	int lca=LCA(u,root);
	if(lca!=u)return query(1,1,n,in[u],out[u]);
	int son=find(u,root);
	return query(1,1,n,1,n)-query(1,1,n,in[son],out[son]);
}

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)val[i]=getint();
	for(int re i=2;i<=n;++i){
		int fa=getint();
		addedge(fa,i);
	}
	tree_dissection();
	build(1,1,n);
	m=getint();
	while(m--){
		int op=getint(),u=getint();
		switch(op){
			case 1:{root=u;break;}
			case 2:{
				int v=getint(),val=getint();
				addpath(u,v,val);
				break;
			}
			case 3:{
				int val=getint();
				addsubtree(u,val);
				break;
			}
			case 4:{
				int v=getint();
				outint(querypath(u,v));pc('\n');
				break;
			}
			case 5:{
				outint(querysubtree(u));pc('\n');
				break;
			}
		}
	}
	return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值