题解[CF916E Jamie and Tree]

题目

Luogu

CF916E

Sol

我们先以\(1\)为根节点建出一棵树来,以下称此树为原树

一个重要结论:

设现在根节点为\(rt\) ,要求点\(u,v\)\(LCA\)

\(LCA(u,v)=maxdeep(lca(u,rt),lca(v,rt),lca(u,v))\) .

\(lca(u,v)\)表示原树(换根前)两点的\(LCA\)

不会整,好像是简单的分类讨论

那我们考虑修改子树操作。

设要修改节点\(u\)的子树:

\(rt\)\(u\)的子树上,那我们找到\(rt\)\(u\)的哪个直接儿子的子树上,除那个儿子对应的子树外,整棵树的其余部分全部修改。

如果没在,那我们直接改就好了。

查询操作:

如果\(rt\)\(u\)的子树上,同理,找到那个包含\(rt\)的儿子,查询树上除这个儿子外所有部分的权值。

如果不在,直接查询\(u\)子树的权值。

树上修改、查询可以用树剖,也可以欧拉序+线段树,这里实现用的是后者。

Code

#include<bits/stdc++.h>
#define ll long long
#define N (500010)
#define V (4000010)
#define M (1000010)
using namespace std;
struct xbk{int ed,nx;}e[M];
struct xll{ll l,r;ll sum,tag;};
struct zxy{int l,r;}a[N];
int n,Q,cnt,rt,t=20,tot=0;
int dfn[N],head[N],ff[N][22],dep[N],fa[N],sz[N];
ll val[N],w[M];
struct Tree{
	xll t[V];
	inline void spread(int p){
		if(t[p].tag==0) return;
		t[p<<1].sum+=(t[p<<1].r-t[p<<1].l+1)*t[p].tag;
		t[p<<1|1].sum+=(t[p<<1|1].r-t[p<<1|1].l+1)*t[p].tag;
		t[p<<1].tag+=t[p].tag;
		t[p<<1|1].tag+=t[p].tag;
		t[p].tag=0;
		return;
	}
	inline void build(int p,ll l,ll r){
		t[p].l=l,t[p].r=r,t[p].tag=0;
		if(l==r){
		    t[p].sum=w[l];
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
		return;
	}
	inline void change(int p,ll l,ll r,ll v){
		if(l<=t[p].l&&r>=t[p].r){
			t[p].sum+=v*(t[p].r-t[p].l+1);
			t[p].tag+=v;
			return;
		}
		spread(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(l<=mid) change(p<<1,l,r,v);
		if(r>mid) change(p<<1|1,l,r,v);
		t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
		return;
	}
	inline ll ask(int p,ll l,ll r){
		if(t[p].l>=l&&t[p].r<=r) return t[p].sum;	
		spread(p);
		int mid=(t[p].l+t[p].r)>>1;
		ll res=0;
		if(l<=mid) res+=ask(p<<1,l,r);
		if(r>mid) res+=ask(p<<1|1,l,r);
		return res;
	}
}T;
inline void add(int a,int b){
	e[++cnt].ed=b;
	e[cnt].nx=head[a];
	head[a]=cnt;
}
inline int LCA(int a,int b){
	if(dep[a]>dep[b]) swap(a,b);
	for(int i=t;i>=0;i--)
		if(dep[ff[b][i]]>=dep[a]) b=ff[b][i];
	if(a==b) return a;
	for(int i=t;i>=0;i--)
		if(ff[a][i]!=ff[b][i]) a=ff[a][i],b=ff[b][i];
	return ff[a][0];
}
inline void dfs(int st,int f){
	dep[st]=dep[f]+1,fa[st]=f,ff[st][0]=f;
	dfn[++tot]=st,a[st].l=tot;
	w[tot]=val[st];
	for(int i=1;i<=t;i++) ff[st][i]=ff[ff[st][i-1]][i-1];
	for(int i=head[st];i;i=e[i].nx){
		int ed=e[i].ed;
		if(ed==f) continue;
		dfs(ed,st);
	}
	dfn[++tot]=st;
	w[tot]=val[st];
	a[st].r=tot;
	return;
}
//倍增找到那个特定的儿子
inline int getfa(int st,int fff){
	for(int i=t;i>=0;i--)
		if(dep[ff[st][i]]>dep[fff]) st=ff[st][i];
	while(dep[fa[st]]>dep[fff]) st=fa[st];
	return st;
}
int main(){
	n=read(),Q=read();
	for(int i=1;i<=n;i++) val[i]=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	dfs(rt=1,0);
	T.build(1,1,tot);
	while(Q--){
		int opt=read();
		if(opt==1) rt=read();
		if(opt==2){
			int u=read(),v=read();
			ll d=read();
			int l1=LCA(u,v),l2=LCA(v,rt),l3=LCA(u,rt),lca;
			if(dep[l1]>dep[l2]&&dep[l1]>dep[l3]) lca=l1;
			else if(dep[l2]>dep[l1]&&dep[l2]>dep[l3]) lca=l2;
			else if(dep[l3]>dep[l2]&&dep[l3]>dep[l1]) lca=l3;
			else if(l1==l2&&l1==l3) lca=l1;
          //找LCA
			if(a[lca].l<a[rt].l&&a[lca].r>a[rt].r){//rt在u的子树上
				int ed=getfa(rt,lca);
				T.change(1,1,tot,d);
				T.change(1,a[ed].l,a[ed].r,-d);
			}
			else if(lca==rt) T.change(1,1,tot,d);//根节点特判
			else T.change(1,a[lca].l,a[lca].r,d);//rt不在u的子树上 
		}
		if(opt==3){			
			int u=read();
			if(u==rt) writeln(T.ask(1,a[1].l,a[1].r)>>1);//根节点特判
			else if(a[u].l<a[rt].l&&a[u].r>a[rt].r){
				int ed=getfa(rt,u);
				writeln((T.ask(1,1,tot)-T.ask(1,a[ed].l,a[ed].r))>>1);
			}
			else writeln(T.ask(1,a[u].l,a[u].r)>>1);//欧拉序上的点都出现了两遍,记得除以二
		}
	}
	return 0;
}
posted @ 2021-04-01 15:47  xxbbkk  阅读(56)  评论(0编辑  收藏  举报