树链剖分换根【模板】

树的操作

描述
XXX和 YYY在愉快地刷题。有一道题是这样的:给你一棵 n 个节点的有根树,每个节点有 一个权植。你要支持两种操作:查询以某棵树为根的子树的权值和,给以某个节点为根的整 棵子树的所有点的权值都加上一个值。机智的 XXX 很开心地用LLL教授 讲过的某些东西水水水水过了这道题。

但是可怕的出题人又增加了一种操作:将根节点改为第 u 号节点。于是XXX和YYY 就不会 做了。按照一惯的逻辑,这个问题被交给了你。

注意:初始时,根节点为 1 号节点,所有节点的权值都为 0。

输入
第一行有三个正整数 n, m,表示节点数和操作数。

接下来 n – 1 行每行两个正整数 p, q,表示 p 和 q 节点间有一条边。

接下来 m 行,每行首先有一个正整数 type,描述操作类型。

如果 type=1,接下来有一个正整数 u,表示询问以 u 为根的子树的权值和。

如果 type=2,接下来有两个正整数 u 和 v,表示把以 u 为根的子树的所有节点的权值都加上 v。

如果 type=3,接下来有一个正整数 u,表示把根节点改为第 u 号节点。

输出
对于每一个 1 号操作,输出一行一个非负整数表示答案。

样例输入
3 4
1 2
2 3
2 2 2
1 3
3 3
1 3
样例输出
2
4
提示
【Hint】

对于 30%的数据,1 ≤ n, m ≤ 1,000。

另有 50%的数据不含有 3 号操作。

对于 100%的数据,1 ≤ n, m ≤ 200,000。

对于 100%的数据,1 ≤ u ≤ n, 1 ≤ v ≤ 500,000, 1 ≤ k ≤ n。


Analysis

分析……没有……(我之前写过啦,今日康复训练)
代码……还是有的


Code
#include<bits/stdc++.h>
#define in read()
#define ll long long
#define N 400009
#define M 400009
#define lc (k<<1)
#define rc (k<<1)|1
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int n,m,tp,rt=1;
int top[N],sze[N],fa[N],dep[N],son[N];
int idx[N],pos[N],cnt=0;
int nxt[M],to[M],head[N],ecnt=0;
struct node{ll lzy,sum;}seg[N<<2];
node operator + (const node &a,const node &b){
	node res;res.lzy=0;
	res.sum=a.sum+b.sum;
	return res;
}
inline void add(int x,int y){
	nxt[++ecnt]=head[x];head[x]=ecnt;to[ecnt]=y;
}
void dfs1(int u,int fu){
	fa[u]=fu;dep[u]=dep[fu]+1;sze[u]=1;
	for(int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fu) continue;
		dfs1(v,u);
		sze[u]+=sze[v];
		if(sze[v]>sze[son[u]]) son[u]=v;
	}
}
void dfs2(int u){
	if(son[u]){
		int v=son[u];
		idx[pos[v]=++cnt]=v;
		top[v]=top[u];
		dfs2(v);
	}
	for(int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fa[u]||v==son[u]) continue;
		top[v]=v;
		idx[pos[v]=++cnt]=v;
		dfs2(v);
	}
}
inline void pushdown(int k,int l,int r){
	int mid=l+r>>1;
	seg[lc].lzy+=seg[k].lzy;seg[lc].sum+=(mid-l+1)*1ll*seg[k].lzy;
	seg[rc].lzy+=seg[k].lzy;seg[rc].sum+=(r-mid)*1ll*seg[k].lzy;
	seg[k].lzy=0;
}
inline node querytree(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y) return seg[k];
	if(seg[k].lzy) pushdown(k,l,r);
	int mid=l+r>>1;
	if(y<=mid) return querytree(lc,l,mid,x,y);
	if(x>mid) return querytree(rc,mid+1,r,x,y);
	return querytree(lc,l,mid,x,y)+querytree(rc,mid+1,r,x,y);
}
inline int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
inline int find(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		if(fa[top[x]]==y) return top[x];
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?son[x]:son[y];
}
inline ll query(int x){
	if(x==rt) return querytree(1,1,n,1,n).sum;//注意特判相等的情况 
	int lca=LCA(x,rt);
	if(lca!=x) return querytree(1,1,n,pos[x],pos[x]+sze[x]-1).sum;
	else{
		int son=find(x,rt);
		ll ans=querytree(1,1,n,1,n).sum;
		ans-=querytree(1,1,n,pos[son],pos[son]+sze[son]-1).sum;
		return ans;
	}
}
inline void modifytree(int k,int l,int r,int x,int y,ll v){
	if(x<=l&&r<=y) {
		seg[k].lzy+=v;
		seg[k].sum+=(r-l+1)*1ll*v;
		return;
	}
	if(seg[k].lzy) pushdown(k,l,r);
	int mid=l+r>>1;
	if(x<=mid) modifytree(lc,l,mid,x,y,v);
	if(y>mid) modifytree(rc,mid+1,r,x,y,v);
	seg[k]=seg[lc]+seg[rc];
}
inline void modify(int x,ll v){
	int lca=LCA(x,rt);
	if(x==rt) return modifytree(1,1,n,1,n,v);
	if(lca!=x) modifytree(1,1,n,pos[x],pos[x]+sze[x]-1,v);
	else{
		int son=find(x,rt);
		modifytree(1,1,n,1,n,v);
		modifytree(1,1,n,pos[son],pos[son]+sze[son]-1,-v);
	}	
}
int main(){
	n=in;m=in;
	int i,j,k,x,y;
	for(i=1;i<n;++i){
		x=in;y=in;
		add(x,y);add(y,x);
	}
	dfs1(1,0);//处理fa,son,sze,dep 
//	for(i=1;i<=n;++i) printf("fa=%d son=%d sze=%d dep=%d\n",fa[i],son[i],sze[i],dep[i]);
	top[1]=1;idx[1]=++cnt;pos[1]=1;//不要忘了处理 1 ,因为后面dfs2的时候只处理了根的儿子 
	dfs2(1);//处理top,pos,idx 
	for(i=1;i<=m;++i){
		tp=in;int u,v;u=in;
		if(tp==1){
			printf("%lld\n",query(u));
		}
		else if(tp==2){
			v=in;
			modify(u,v);
		}
		else rt=u;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值