CF916E Jamie and Tree 题解

CF916E Jamie and Tree 题解

CF916E Jamie and Tree

题意

有一棵 \(n\) 个节点的有根树,标号为 \(1-n\),你需要维护以下三种操作

  1. 给一个点 \(v\) ,将整颗树的根变为 \(v\)

  2. 给两个点 \(u,v\),将 \(lca(u,v)\) 所在的子树都加上 \(x\)

  3. 给一个点 \(v\),问以 \(v\) 所在的子树的权值和。

分析

换根树剖板子题。设当前根为 \(rt\) ,有几种简单操作:

换根后 LCA

直接分讨即可。

int LCA(int x,int y)
{
    if(dep[x]>dep[y]) swap(x,y);
    int xr=lca(x,rt),yr=lca(y,rt),xy=lca(x,y);
    if(xy==x) 
    {
        if(xr==x){if(yr==y) return y;return yr;}
        return x;
    }
    if(xr==x) return x;if(yr==y) return y;
    if((xr==rt&&xy==yr)||(yr==rt&&xy==xr)) return rt;
    if(xr==yr) return xy;
    if(xy!=xr) return xr;return yr;
}

换根后子树修改

分几种情况讨论:

  1. \(u\)\(rt\)

此时直接把所有点权都加上 \(x\)

  1. \(u\) 不是 \(rt\) 的祖先

此时情况如图所示,不难发现,无论 \(u\) 在哪,\(u\) 的子树都不变。

  1. \(u\)\(rt\) 的祖先

此时情况如图所示,设 \(v\) 为是 \(rt\) 祖先的 \(u\) 的子节点,要想得到 \(v\) ,我们可以每次把 \(rt\) 向上跳重链,直到跳到 \(u\) 的子节点。可以发现,加的部分相当于整棵树刨掉 \(v\) 的子树,由于树剖之后子树内节点编号连续,所以可以用线段树区间修改,每次修改 \([1,id_v) \bigcup (id_v+cnt_v-1,n]\)

此部分代码如下:

int get(int x,int y)
{
    int fx=top[x],fy=top[y];
    while(top[x]!=top[y])
    {
        if(dep[fx]<dep[fy]) swap(fx,fy),swap(x,y); //跳重链顶深度更深的
        if(fa[fx]==y) return fx; //已经找到了
        x=fa[fx],fx=top[x];
    }
    if(dep[x]>dep[y]) swap(x,y);
    return hson[x];
}
void update_subtree(int u,int k)
{
    if(u==rt) update(1,1,n,1,n,k); //对应情况1
    else if(lca(u,rt)!=u) update(1,1,n,id[u],id[u]+cnt[u]-1,k); //情况2
    else //情况3
    {
        int v=get(u,rt);
        if(id[v]-1>=1) update(1,1,n,1,id[v]-1,k);
        if(id[v]+cnt[v]<=n) update(1,1,n,id[v]+cnt[v],n,k);
    }
}

换根后子树求和

原理和子树修改相同。

int query_subtree(int u)
{
    int res=0;
    if(u==rt) res=query(1,1,n,1,n);
    else if(lca(u,rt)!=u) res=query(1,1,n,id[u],id[u]+cnt[u]-1);
    else
    {
        int v=get(u,rt);
        if(id[v]-1>=1) res+=query(1,1,n,1,id[v]-1);
        if(id[v]+cnt[v]<=n) res+=query(1,1,n,id[v]+cnt[v],n);
    }
    return res;
}

完整代码

Submission #153363241 - Codeforces

posted @ 2022-04-11 18:39  l_x_y  阅读(46)  评论(0编辑  收藏  举报