参考博客:

http://www.cnblogs.com/zwfymqz/p/8094500.html

https://www.cnblogs.com/ivanovcraft/p/9019090.html

http://www.cnblogs.com/chinhhh/p/7965433.html

定义

重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;

轻儿子:父亲节点中除了重儿子以外的儿子;

重边:父亲结点和重儿子连成的边;

轻边:父亲节点和轻儿子连成的边;

重链:由多条重边连接而成的路径;

  • 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
  • 每一条重链以轻儿子为起点

轻链:由多条轻边连接而成的路径;

如何对这些链进行维护?

首先,要对这些链进行维护,就要确保每个链上的节点都是连续的,因此我们需要对整棵树进行重新编号,然后利用dfs序的思想,用线段树或树状数组等进行维护。

对于一棵最基本的树:

给他标记重儿子:

蓝色为重儿子,红色为重边。

然后对树进行重新编号:

橙色表示的是该节点重新编号后的序号。

不难看出重链内的节点编号是连续的。

有一个性质:以i为根的子树的树在线段树上的编号为[i,i+子树节点数−1]

步骤

1.对整棵树dfs一遍,找出每个节点的重儿子。处理出每个节点的深度,以及他们的父亲节点。

处理出这些:

int par[100010];//该结点的父亲,用来往上跳
int dep[100010];//该结点的深度,用来往上跳
int tot[100010];//该结点子树的结点个数,用来更新权值和
int son[100010];//该结点的重儿子编号

2.对整棵树进行重新编号。

处理出这些:

int idx[100010];//该结点重新编号后的编号
int topp[100010];//该结点所在链的顶端结点
int aval[100010];//重新编号后该结点的权值

3.根据重新编完号的树,把这棵树的上每个点映射到线段树上。并写出线段树的基本操作函数。

4.实现对于树上的操作,

树链剖分的思想是:对于两个不在同一重链内的节点,让他们不断地跳,使得他们处于同一重链上

那么如何”跳”呢?

用到top数组么?

有一个显然的结论:x到top[x]中的节点在线段树上是连续的。

结合deep数组,

假设两个节点为x,y,

我们每次让deep[top[x]]与deep[top[y]]中大的(在下面的)往上跳(有点类似于树上倍增),

让x节点直接跳到top[x],然后在线段树上更新,

最后两个节点一定是处于同一条重链的,前面我们提到过重链上的节点都是连续的,直接在线段树上进行一次查询就好。

结合一个例子说明:

还是上面那张图,假设我们要查询3.6这两个节点的之间的点权合,为了方便理解我们假设每个点的点权都是1,

刚开始时

top[3]=2,top[6]=1,

deep[top[3]]=2,deep[top[6]]=1,

我们会让3向上跳,跳到top[3]的爸爸,也就是1号节点

这是1号节点和6号节点已经在同一条重链内,所以直接对线段树进行一次查询即可。

对于子树的操作,利用性质直接query或update。

复杂度

模板

已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z;

2 x y 表示求树从x到y结点最短路径上所有节点的值之和;

3 x z 表示将以x为根节点的子树内所有节点值都加上z;

4 x 表示求以x为根节点的子树内所有节点值之和。

int val[100010];//该结点的权值
int top;
int head[100010];
struct edge
{
    int to;
    int next;
}eg[200010];
int par[100010];//该结点的父亲
int dep[100010];//该结点的深度
int tot[100010];//该结点子树的结点个数
int son[100010];//该结点的重儿子编号
int idx[100010];//该结点重新编号后的编号
int topp[100010];//该结点所在链的顶端结点
int aval[100010];//重新编号后该结点的权值
int cnt;
struct node
{
    int left,right;
    int sum;
    int f;
}node[100010<<2];
int ans;
void add(int a,int b)
{
    eg[top].to=b;
    eg[top].next=head[a];
    head[a]=top++;
}
int dfs1(int id,int fa,int depth)
{
    par[id]=fa;dep[id]=depth;
    tot[id]=1;
    int maxson=-1;
    son[id]=0;
    for(int i=head[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(temp==fa)continue;
        tot[id]+=dfs1(temp,id,depth+1);
        if(tot[temp]>maxson)
        {
            son[id]=temp;
            maxson=tot[temp];
        }
    }
    return tot[id];
}
void dfs2(int id,int topf)
{
    idx[id]=++cnt;
    aval[cnt]=val[id];
    topp[id]=topf;
    if(son[id]==0)return;     //子节点的son为0
    dfs2(son[id],topf);     //优先标记重儿子
    for(int i=head[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(idx[temp]==0)
            dfs2(temp,temp);     //每一条链以轻儿子为起点
    }
}
void build(int l,int r,int id)
{
    node[id].left=l;node[id].right=r;
    node[id].f=0;
    if(l==r)
    {
        node[id].sum=aval[l];
        return;
    }
    int m=(l+r)>>1;
    build(l,m,id<<1);
    build(m+1,r,id<<1|1);
    node[id].sum=node[id<<1].sum+node[id<<1|1].sum;
}
void pushdown(int id)
{
    int l1=node[id<<1].left,r1=node[id<<1].right;
    int l2=node[id<<1|1].left,r2=node[id<<1|1].right;
    node[id<<1].f=node[id<<1].f+node[id].f;
    node[id<<1|1].f=node[id<<1|1].f+node[id].f;
    node[id<<1].sum=node[id<<1].sum+node[id].f*(r1-l1+1);
    node[id<<1|1].sum=node[id<<1|1].sum+node[id].f*(r2-l2+1);
    node[id].f=0;
}
void update(int L,int R,int c,int id)
{
    int l=node[id].left,r=node[id].right;
    if(l>=L&&r<=R)
    {
        node[id].sum=node[id].sum+c*(r-l+1);
        node[id].f=node[id].f+c;
        return;
    }
    if(node[id].f)pushdown(id);
    int m=(l+r)>>1;
    if(L<=m)update(L,R,c,id<<1);
    if(R>m)update(L,R,c,id<<1|1);
    node[id].sum=node[id<<1].sum+node[id<<1|1].sum;
}
void query(int L,int R,int id)
{
    int l=node[id].left,r=node[id].right;
    if(l>=L&&r<=R)
    {
        ans=ans+node[id].sum;
        return;
    }
    if(node[id].f)pushdown(id);
    int m=(l+r)>>1;
    if(L<=m)query(L,R,id<<1);
    if(R>m)query(L,R,id<<1|1);
}
void change1(int x,int y,int c)
{
    while(topp[x]!=topp[y])
    {
        if(dep[topp[x]]<dep[topp[y]])swap(x,y);     //谁的top低谁就往上走
        update(idx[topp[x]],idx[x],c,1);     //改变x~top[x]这条链的权值
        x=par[topp[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(idx[x],idx[y],c,1);     //改变x~top[x]这条链的权值
}
void change2(int id,int c)
{
    update(idx[id],idx[id]+tot[id]-1,c,1);
}
int ask1(int x,int y)
{
    int res=0;
    while(topp[x]!=topp[y])
    {
        if(dep[topp[x]]<dep[topp[y]])swap(x,y);   //谁的top低谁就往上走
        ans=0;
        query(idx[topp[x]],idx[x],1);     //查询x~top[x]这条链的权值
        res=res+ans;
        x=par[topp[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    ans=0;
    query(idx[x],idx[y],1);     //查询x~top[x]这条链的权值
    res=res+ans;
    return res;
}
int ask2(int id)
{
    ans=0;
    query(idx[id],idx[id]+tot[id]-1,1);
    return ans;
}
int main()
{
    int n,m,r,op,x,y,z;
    scanf("%d%d%d",&n,&m,&r);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&val[i]);
        head[i]=-1;
        idx[i]=0;
    }
    top=0;
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs1(r,0,1);
    cnt=0;
    dfs2(r,r);
    build(1,n,1);
    while(m--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            change1(x,y,z);     //将树从x到y结点最短路径上所有节点的值都加上z
        }
        else if(op==2)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",ask1(x,y));     //求树从x到y结点最短路径上所有节点的值之和
        }
        else if(op==3)
        {
            scanf("%d%d",&x,&z);
            change2(x,z);     //将以x为根节点的子树内所有节点值都加上z
        }
        else if(op==4)
        {
            scanf("%d",&x);
            printf("%d\n",ask2(x));     //求以x为根节点的子树内所有节点值之和
        }
    }
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

HDU6435 k维最远曼哈顿距离 Previous
CTU2017D 哈希+乱搞 Next