浅谈算法——树链剖分
前言
首先树链剖分需要使用到线段树知识,不会线段树的童鞋请移步至浅谈算法——线段树
在做题中我们会看到一些“在一棵树上进行路径修改、求极值、求和”的题,乍一看能够用线段树解决,其实仅仅凭线段树是根本无法完成的。这时候,我们就需要用到一种看起来高级的复杂算法——树链剖分
基本概念
重儿子:size[u](表示以u为根节点的子树节点个数)为v的子节点中最大的,称u为v的重儿子
轻儿子:v的其他子节点
重边:v与其重儿子的连边
轻边:v与其轻儿子的连边
重链:重边组成的一条链
如图所示,圆圈内为size,粗边为重边,我们称某条路径为重链,当且仅当它全部由重边组成。不存在轻链这一说法
任意一个点到根的路径上,不超过\(\log n\)条轻边,也不超过\(\log n\)条重链,这是整个算法时间复杂度的保证,证明略。因为我也不会证明
基本操作
树链:树上的路径
剖分:把路径分为两类
树链剖分的操作为两次dfs
- 找重边
- 把重边连成重链
记size[v]表示以v为根的儿子节点的个数,deep[v]表示v的深度(根深度为1),top[v]表示v所在重链的顶端节点,fa[v]表示v的父亲节点,Rem[v]表示v的重儿子,ID[v]表示v的新编号
第一遍dfs将size,deep,fa,Rem求出来
第二遍dfs的时候,每次优先dfs重儿子,这样可以使得重儿子的编号连续
如下图示,粗边为重边,细边为轻边。节点边带红点的说明其为该条重链的顶端,蓝色数字代表其新编号
为什么要按这种方式去进行树链剖分?
因为在剖分完之后,我们可以发现重链上的点的编号是连续的,所以我们可以用线段树或其他高级数据结构去维护重链上的信息,由于每条重链所占据的编号互不相同,所以我们可以把这些重链首尾相连,放到一个高级数据结构上进行维护。同时我们认为一个点也属于一条重链。轻边的信息可以直接维护。因为它不好维护,除非把边权下移作为点权
树上基本操作
单点修改:直接按新编号对应单点修改即可
路径修改:
1、u和v在同一条重链上,直接线段树区间修改即可
2、u和v不在同一条重链上,我们要想办法把它们往同一条重链上靠
- fa[top[u]]与v在同一条重链上,两次做区间修改即可
- u经过若干条重链与轻边后和v在同一条重链上,多次做区间修改即可
- u和v都经过若干条重链与轻边后在同一条重链上,那么每次找到深度较深的点x,做一次区间修改后,在将该点跳到fa[top[x]],知道u和v在同一条重链上
前两种情况是特殊的第三种情况。
查询操作:和路径修改类似,求极值,最大值最小值等,在线段树操作时按照具体情况修改即可
由于经过的重链个数不会超过\(\log n\)级别,因此整个树链剖分的复杂度为\(O(n\log^2n)\),但实际上比某些\(O(n\log n)\)的算法要快
例题
Description
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
Input
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
Output
输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)
Sample Input
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
Sample Output
2
21
HNIT
对于100%的数据:$ N \leq {10}^5, M \leq {10}^5$
故输出应依次为2、21(重要的事情说三遍:记得取模)
/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x>=10) print(x/10);
putchar(x%10+'0');
}
const int N=1e5;
int v[N+10],dfn[N+10],Line[N+10];
int n,m,root,mod;
struct Segment{//线段树Lazy标记操作
#define ls (p<<1)
#define rs ((p<<1)|1)
int tree[(N<<2)+10],Lazy[(N<<2)+10];
void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
void add_tag(int p,int l,int r,int v){
tree[p]=(tree[p]+1ll*(r-l+1)*v)%mod;
Lazy[p]=(Lazy[p]+v)%mod;
}
void pushdown(int p,int l,int r){
if (!Lazy[p]) return;
int mid=(l+r)>>1;
add_tag(ls,l,mid,Lazy[p]),add_tag(rs,mid+1,r,Lazy[p]);
Lazy[p]=0;
}
void build(int p,int l,int r){
if (l==r){tree[p]=v[Line[l]]%mod;return;}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
updata(p);
}
void insert(int p,int l,int r,int x,int y,int v){
if (x<=l&&r<=y){add_tag(p,l,r,v);return;}
int mid=(l+r)>>1;
pushdown(p,l,r);
if (x<=mid) insert(ls,l,mid,x,y,v);
if (y>mid) insert(rs,mid+1,r,x,y,v);
updata(p);
}
int query(int p,int l,int r,int x,int y){
if (x<=l&&r<=y) return tree[p];
pushdown(p,l,r);
int mid=(l+r)>>1,res=0;
if (x<=mid) res=(res+query(ls,l,mid,x,y))%mod;
if (y>mid) res=(res+query(rs,mid+1,r,x,y))%mod;
return res;
}
}Tree;
struct Start{
int pre[(N<<1)+10],child[(N<<1)+10],now[N+10];
int deep[N+10],size[N+10],fa[N+10],Rem[N+10],top[N+10];
int tot,cnt;
void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y;}
void build(int x,int Deep){//第一遍dfs
deep[x]=Deep,size[x]=1;
for (int p=now[x],son=child[p],Max=0;p;p=pre[p],son=child[p]){
if (son==fa[x]) continue;
fa[son]=x;
build(son,Deep+1);
size[x]+=size[son];
if (Max<size[son]) Max=size[son],Rem[x]=son;
}
}
void dfs(int x){//第二遍dfs
if (!x) return;
Rem[fa[x]]==x?top[x]=top[fa[x]]:top[x]=x;
dfn[x]=++cnt,Line[cnt]=x;
dfs(Rem[x]);
for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
if (son==fa[x]||son==Rem[x]) continue;
dfs(son);
}
}
void insert(int x,int y,int z){//区间操作
while (top[x]!=top[y]){
if (deep[top[x]]<deep[top[y]]) swap(x,y);
Tree.insert(1,1,n,dfn[top[x]],dfn[x],z);
x=fa[top[x]];
}
if (deep[x]>deep[y]) swap(x,y);
Tree.insert(1,1,n,dfn[x],dfn[y],z);
}
int query(int x,int y){//区间查询
int res=0;
while (top[x]!=top[y]){
if (deep[top[x]]<deep[top[y]]) swap(x,y);
res=(res+Tree.query(1,1,n,dfn[top[x]],dfn[x]))%mod;
x=fa[top[x]];
}
if (deep[x]>deep[y]) swap(x,y);
res=(res+Tree.query(1,1,n,dfn[x],dfn[y]))%mod;
return res;
}
}T;
int main(){
n=read(),m=read(),root=read(),mod=read();
for (int i=1;i<=n;i++) v[i]=read();
for (int i=1,x,y;i<n;i++) x=read(),y=read(),T.join(x,y),T.join(y,x);
T.build(root,1),T.dfs(root),Tree.build(1,1,n);
for (int i=1;i<=m;i++){
int t=read();
if (t==1){
int x=read(),y=read(),z=read();
T.insert(x,y,z);
}
if (t==2){
int x=read(),y=read();
printf("%d\n",T.query(x,y));
}
if (t==3){//子树修改
int x=read(),z=read();
Tree.insert(1,1,n,dfn[x],dfn[x]+T.size[x]-1,z);
}
if (t==4){//子树查询
int x=read();
printf("%d\n",Tree.query(1,1,n,dfn[x],dfn[x]+T.size[x]-1));
}
}
return 0;
}