【JZOJ5783】树

37 篇文章 0 订阅
12 篇文章 0 订阅

Description

一棵树,支持换根,给u,v的lca的子树的所有结点加上一个权值x,维护子树和。

Solution

把树放到dfs序上,我们不用真的换根,只要记录根的位置。如果根在lca子树外不会有影响,在lca子树内就找到根到lca路径上深度比lca大1的点,除了这个点往下的子树其它都要修改,在dfs序上就是至多两段区间的修改。注意常数。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
#define rep(i,x) for(int i=ls[x];i;i=nx[i])
using namespace std;
typedef long long ll;
const int N=3e5+10,M=6e5+10;
int to[M],nx[M],ls[N],num=0;
int w[N];
int read(){
    char ch=' ';int t=0,c=1;
    for(;(ch<'0' || ch>'9') && ch!='-';ch=getchar());
    if(ch=='-') c=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar()) t=(t<<1)+(t<<3)+ch-48;
    return t*c;
}
void link(int u,int v){
    to[++num]=v,nx[num]=ls[u],ls[u]=num;
}
int L[N],R[N],tot=0;
int f[N][20],dep[N];
int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    fd(i,19,0) if(dep[f[u][i]]>=dep[v]) u=f[u][i];
    if(u==v) return u;
    fd(i,19,0) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
    return f[u][0];
}
void pre(int x,int fr){
    L[x]=++tot,f[x][0]=fr,dep[x]=dep[fr]+1;
    fo(i,1,19) f[x][i]=f[f[x][i-1]][i-1];
    rep(i,x){
        int v=to[i];
        if(v==fr) continue;
        pre(v,x);
    }
    R[x]=tot;
}
int rt,n;
ll c1[N],c2[N];
void up(ll *a,int x,ll t){
    for(;x<=n;x+=x&-x) a[x]+=t;
}
ll calc(ll *a,int x){
    ll tmp=0;
    for(;x;x-=x&-x) tmp+=a[x];
    return tmp;
}
void add(int l,int r,int x){
    up(c1,l,x),up(c1,r+1,-x);
    up(c2,l,(ll)x*(l-1)),up(c2,r+1,-(ll)x*r);
}
ll sum(int l,int r){
    if(l>r) return 0;
    ll s1=calc(c1,l-1)*(l-1)-calc(c2,l-1);
    ll s2=calc(c1,r)*r-calc(c2,r);
    return s2-s1;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int q;
    n=read(),q=read();
    fo(i,1,n) w[i]=read();
    fo(i,2,n){
        int u,v;
        u=read(),v=read();
        link(u,v),link(v,u);
    }
    tot=0,rt=1,pre(1,0);
    fo(i,1,n) add(L[i],L[i],w[i]);
    while(q--){
        int op=read();
        if(op==1) rt=read();
        else if(op==2){
            int u=read(),v=read(),x=read();
            int lc=lca(u,v),tp=lca(u,rt);
            if(dep[lc]<dep[tp]) lc=tp;
            tp=lca(v,rt);
            if(dep[lc]<dep[tp]) lc=tp;
            if(rt==lc) add(1,n,x);
            else if(L[rt]<L[lc] || L[rt]>R[lc]) add(L[lc],R[lc],x);
            else{
                int p=rt;
                fd(i,19,0) if(dep[f[p][i]]>dep[lc]) p=f[p][i];
                add(1,L[p]-1,x),add(R[p]+1,n,x);
            }
        }
        else{
            int x=read();
            if(rt==x) printf("%lld\n",sum(1,n));
            else if(L[rt]<L[x] || L[rt]>R[x]) printf("%lld\n",sum(L[x],R[x]));
            else{
                int p=rt;
                fd(i,19,0) if(dep[f[p][i]]>dep[x]) p=f[p][i];
                printf("%lld\n",sum(1,L[p]-1)+sum(R[p]+1,n));
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值