题解[CF916E Jamie and Tree]
题目
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;
}