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
- Linked List/Chained Forward Star
- Line segment tree/tree array/Splay, etc. can maintain a data structure of a piece of data
- 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):
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
- determine the depth of this point
- Determine the parent node
- Determine the number of nodes in the subtree rooted at this node
- 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
- Determine the new number (dfs order)
- assign a value to the new number
- Determine the top of the chain where this point is located
- 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
- In some problems without a specified root, it is possible to use any node as the root
- The root node can start dfs with 0 as its root, and the top is itself
- 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……