Summary of tree chain segmentation

As a konjac who just finished learning line segment tree last month, but I couldn't understand it when I watched Splay, so I jumped to learn tree sectioning. I saw on a blog that it is best to learn LCA before learning tree dissection, so I went to spend a day learning a Tarjan to ask for LCA (but later found out that it is not very necessary), and then almost followed others. The code dissects the tree and understands it. Here I will talk about the tree section as I understand it.


Ready to work

  1. Linked List/Chained Forward Star
  2. Line segment tree/tree array/Splay, etc. can maintain a data structure of a piece of data
  3. LCA (in fact, it only involves some ideas, and it is hardly used, so you can not learn it first)

tree section principle

I heard about the tree section a long time ago, and I thought it was too tall at the time, but now I find that the name is relatively high-end, the whole principle is still very simple, but the code size is relatively large.

As a konjac, I can only search almost all of the graph topics, and I got only a few points on the SCOI by searching. However, I still learned about prefixes and differences. If all the points in a graph are connected into a line, then to find the path length between two points, it is only necessary to maintain the prefix sum. If the nodes in a tree are connected by a line, we call it a chain. If the nodes on the tree are divided into several chains, many problems can be made much simpler.

The so-called tree chain division is to divide a tree into several chains, and then deal with them separately.

Of course, a node can also be counted as a chain, but if so, it is better not to divide. The purpose of the tree splitting algorithm is to divide each non-leaf node in a tree into a chain, and each node belongs to only one chain, so that the query can be much faster.

Here is a tree (picture source Baidu Encyclopedia):

tree section

In this diagram, the thick lines are the split chains. To get every non-leaf node on the chain, we need to make a chain cover as many nodes as possible. Therefore, in the child nodes of each node, we select the child node with the largest number of subtree nodes with it as the root to form a chain.

For example, in the child node {8,9,10} of node 4, the total number of nodes in the subtree rooted at 8 and 10 is 1, and the number of nodes in the subtree rooted at 9 is 3, so We will continue to connect down 9 as a node on the chain. 9 is called the heavy node of node 4, and the other two nodes are called light nodes

Continue to expand, the connection between the parent node and the heavy node is called the heavy edge , which is the thick line; the connection between the parent node and the light node is called the light edge , which is the thin line in the graph; multiple heavy edges are connected The path is called heavy chain , such as path 1->4->9->13->14; the path connected by multiple light edges is called light chain , such as path 1->2->5.

Summarize these definitions in a table

definition meaning
heavy node The node with the largest number of subtree nodes rooted at it
light node Nodes that are not heavy nodes among all child nodes
heavy edge The connection between the parent node and the heavy node
light edge The connection between the parent node and the light node
heavy chain Paths connected by multiple edges
light chain Paths connected by multiple light edges

accomplish

We can achieve segmentation by using dfs twice, but it is useless to only segment. Generally, problems such as the sum of weights between two nodes and the maximum value of weights are involved. Here is an example of Luogu P3384 [Template] Tree Chain Segmentation.

subdivision

First let me explain the variables I use

variable name significance
fax] father of node x
son[x] Heavy child of node x (node)
size[x] The number of nodes in the subtree rooted at node x
deep[x] depth of node x
top[x] The node number at the top of the chain where node x is located
w[x] The original weight of node x
wnew[x] The weight of the xth node in the dfs order
id[x] dfs order of node x
edge [] sum head [] Chained forward star array
tree[] segment tree

For the first dfs, the tasks that need to be completed are

  1. determine the depth of this point
  2. Determine the parent node
  3. Determine the number of nodes in the subtree rooted at this node
  4. Determine the heavy son of this point

For the specific implementation, see the code

void dfs1(int x,int f,int depth)
{
    deep[x]=depth;//深度
    fa[x]=f;//父亲节点
    size[x]=1;//子树节点个数至少有一个
    int mx=-1;
    for (int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if (v==f) continue;//不搜索父节点
        dfs1(v,x,depth+1);
        size[x]+=size[v];//节点个数加上子节点的
        if (size[v]>mx) //更新重儿子
        {
            mx=size[v];
            son[x]=v;
        }
    }
}

Then the second dfs. The task that needs to be done is

  1. Determine the new number (dfs order)
  2. assign a value to the new number
  3. Determine the top of the chain where this point is located
  4. process each chain

Just look at the code

void dfs2(int x,int topf)
{
    id[x]=++dfsord;//标记每一个节点的dfs序
    wnew[dfsord]=w[x];//得到新编号(dfs序)
    top[x]=topf;//得到这条链的顶端
    if (!son[x]) return;//无儿子返回
    dfs2(son[x],topf);//先处理重儿子
    for (int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if (v==fa[x] || v==son[x]) continue;
        dfs2(v,v);//如果是轻儿子,新的链一定以自己为顶端
    }
}

The heavy son is processed first to ensure that each chain is processed continuously

Okay, the dissection is over, isn't it easy ?

solving issues

Operation 1, 2

Operation 1: Format: 1 xyz means add z to the value of all nodes on the shortest path of the tree from x to y

Operation 2: Format: 2 xy means to find the sum of the values ​​of all nodes on the shortest path from node x to node y in the tree

When processing all nodes on the path between u and v, we generally let u and v belong to the same chain first, and then because of the characteristics of the dfs order, we can directly use the line segment tree to process.

The way to make u and v top the same is to jump the deeper nodes of u and v up above the top of the chain, jumping one and then swapping the next. Every time you change or query, you only need to change the node that is currently jumping to the top of the chain where it is located, and then you can directly deal with the two points after you reach a chain.

code show as below:

Note: My line segment tree is a left-closed and right-open interval, which means [left, right), so the right side should be +1 when processing

Operation 1

void uprange(int u,int v,int delta)
{
    delta%=p;//按题意取%
    while (top[u]!=top[v])
    {
        if (deep[top[u]]<deep[top[v]]) swap(u,v);//交换为更深的点
        change(1,id[top[u]],id[u]+1,delta);
        u=fa[top[u]];//向上跳
    }
    if (deep[u]>deep[v]) swap(u,v);//交换为更深的点保证u的dfs序在前
    change(1,id[u],id[v]+1,delta);
}

Operation 2

int qrange(int u,int v)//求u到v节点的路径中节点之和
{
    int ans=0;
    while (top[u]!=top[v])//不停将u向上跳,直到在一条链上
    {
        if (deep[top[u]]<deep[top[v]]) swap(u,v);//交换成更深的点
        ans+=query(1,id[top[u]],id[u]+1);
        ans%=p;
        u=fa[top[u]];//向上跳
    }
    //已经在一条链上
    if (deep[u]>deep[v]) swap(u,v);
    ans+=query(1,id[u],id[v]+1);
    ans%=p;
    return ans;
}

Operation 3, 4

According to the nature of the dfs order, the right endpoint of the subtree interval is id[x]+siz[x]-1, which can be processed directly.

Code:

Operation 3

inline void upson(int u,int delta)
{
    change(1,id[u],id[u]+size[u],delta);
}

Operation 4

inline int qson(int u)
{
    return query(1,id[u],id[u]+size[u]);
}

other details

  1. In some problems without a specified root, it is possible to use any node as the root
  2. The root node can start dfs with 0 as its root, and the top is itself
  3. Be sure to remember to build a tree with dfs first , because the segment tree handles the order of dfs
dfs1(root,0,1);
dfs2(root,root);
build(1,1,n+1);

full code

#include<bits/stdc++.h>
using namespace std;
struct Edge{
    int next,to;
} edge[200005];
int fa[200005],size[200005],deep[200005],w[200005],wnew[200005],head[200005],son[200005],id[200005],top[200005];
struct Tree{
    int left,right,sum,delta;
} tree[800005];
int cnt=1,ans,n,m,a,b,c,d,p,dfsord,root;
inline void add(int u,int v)
{
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
void build(int x,int l,int r)
{
    tree[x].left=l;
    tree[x].right=r;
    if (r-l==1) tree[x].sum=wnew[l];
    else
    {
        build(x*2,l,(l+r)/2);
        build(x*2+1,(l+r)/2,r);
        tree[x].sum=(tree[x*2].sum+tree[x*2+1].sum)%p;
    }
}
inline void update(int x)
{
    tree[x*2].sum+=tree[x].delta*(tree[x*2].right-tree[x*2].left);
    tree[x*2+1].sum+=tree[x].delta*(tree[x*2+1].right-tree[x*2+1].left);
    tree[x*2].sum%=p;
    tree[x*2+1].sum%=p;
    tree[x*2].delta+=tree[x].delta;
    tree[x*2+1].delta+=tree[x].delta;
    tree[x].delta=0;
}
void change(int x,int l,int r,int delta)
{
    if (l<=tree[x].left && r>=tree[x].right) 
    {
        tree[x].delta+=delta;
        tree[x].sum+=delta*(tree[x].right-tree[x].left);
        tree[x].sum%=p;
    }
    else
    {
        if (tree[x].delta!=0) update(x);
        if (l<(tree[x].left+tree[x].right)/2) change(x*2,l,r,delta);
        if (r>(tree[x].left+tree[x].right)/2) change(x*2+1,l,r,delta);
        tree[x].sum=(tree[x*2].sum+tree[x*2+1].sum)%p;
    }
}
int query(int x,int l,int r)
{
    if (l<=tree[x].left && r>=tree[x].right) return tree[x].sum%p;
    else
    {
        if (tree[x].delta!=0) update(x);
        int ans=0;
        if (l<(tree[x].left+tree[x].right)/2) ans+=query(x*2,l,r);
        if (r>(tree[x].left+tree[x].right)/2) ans+=query(x*2+1,l,r);
        return ans%p;
    }
}               
/*  dfs1
    标记每个点的深度dep[]   
    标记每个点的父亲fa[]
    标记每个非叶子节点的子树大小(含它自己)    
    标记每个非叶子节点的重儿子编号son[]
*/
void dfs1(int x,int f,int depth)
{
    deep[x]=depth;
    fa[x]=f;
    size[x]=1;
    int mx=-1;
    for (int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if (v==f) continue;//不搜索父节点
        dfs1(v,x,depth+1);
        size[x]+=size[v];
        if (size[v]>mx) //更新重儿子
        {
            mx=size[v];
            son[x]=v;
        }
    }
}
/*  dfs2
    标记每个点的新编号
    赋值每个点的初始值到新编号上
    处理每个点所在链的顶端
    处理每条链
*/
void dfs2(int x,int topf)
{
    id[x]=++dfsord;//标记每一个节点的dfs序
    wnew[dfsord]=w[x];//得到新编号(dfs序)
    top[x]=topf;//得到这条链的顶端
    if (!son[x]) return;//无儿子返回
    dfs2(son[x],topf);//先处理重儿子
    for (int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if (v==fa[x] || v==son[x]) continue;
        dfs2(v,v);//如果是轻儿子,新的链一定以自己为顶端
    }
}
int qrange(int u,int v)//求u到v节点的路径中节点之和
{
    int ans=0;
    while (top[u]!=top[v])//不停将u向上跳,直到在一条链上
    {
        if (deep[top[u]]<deep[top[v]]) swap(u,v);//交换成更深的点
        ans+=query(1,id[top[u]],id[u]+1);
        ans%=p;
        u=fa[top[u]];//向上跳
    }
    //已经在一条链上
    if (deep[u]>deep[v]) swap(u,v);
    ans+=query(1,id[u],id[v]+1);
    ans%=p;
    return ans;
}
void uprange(int u,int v,int delta)
{
    delta%=p;
    while (top[u]!=top[v])
    {
        if (deep[top[u]]<deep[top[v]]) swap(u,v);
        change(1,id[top[u]],id[u]+1,delta);
        u=fa[top[u]];
    }
    if (deep[u]>deep[v]) swap(u,v);
    change(1,id[u],id[v]+1,delta);
}
inline int qson(int u)
{
    return query(1,id[u],id[u]+size[u]);//子树区间右端点为id[x]+siz[x]-1 
}
inline void upson(int u,int delta)
{
    change(1,id[u],id[u]+size[u],delta);
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d%d%d",&n,&m,&root,&p);
    for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    for (int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs1(root,0,1);
    dfs2(root,root);
    build(1,1,n+1);
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&a);
        if (a==1)
        {
            scanf("%d%d%d",&b,&c,&d);
            uprange(b,c,d);
        }
        if (a==2)
        {
            scanf("%d%d",&b,&c);
            //cout<<wnew[id[b]]<<" "<<wnew[id[c]]<<endl;
            printf("%d\n",qrange(b,c));
        }
        if (a==3)
        {
            scanf("%d%d",&b,&c);
            upson(b,c);
        }
        if (a==4)
        {
            scanf("%d",&b);
            printf("%d\n",qson(b));
        }
    }
    return 0;
}

Related Examples

pending upgrade……

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324488019&siteId=291194637