【数据结构】树链剖分
Definition
在处理树上的链上修改与询问问题时,如果朴素地采用LCA的手段,那么询问的复杂度是\(O(logn)\),但是修改的复杂度会成为朴素地\(O(n)\),这在大部分题目中是难以接受的。用于处理树上两点间简单路径上权值和与单点子树权值和的修改以及其查询问题的数据结构与处理方法,被叫做树链剖分。
Solution
考虑在一位的数列上做区间查询与修改,可以使用线段树做到每次修改\(O(logn)\)的复杂度。那么考虑在树上能否使用线段树。最简单的思想是给每个节点一个DFS序,即在DFS时给每个点打上时间戳,那么可以将时间戳对应到线段树的区间上,对其进行操作。但是考虑时间戳是散乱的,对于树上的长度为\(n\)一段链,他们的dfs序可能会最多对应\(n\)个区间。对每个区间进行暴力查询与修改的复杂度会达到每次\(O(nlogn)\),对于一个有\(n\)次操作的问题,复杂度会高达\(O(n^2logn)\),这也是无法接受的。那么考虑减少链上对应的区间。可以使用一种方法将区间的个数减小至与\(logn\)同阶,这样的方法就是树链剖分。
下面给出一些定义:
重节点:在一个节点所有儿子中,子树最大的儿子是它的重儿子,这个儿子是一个重节点
轻节点:所有不是重节点的节点是轻节点。
重边:链接两个重节点的边是重边。
重链:将相邻的重边连起来可以形成重链。
规定所有的重链以轻节点为开头。
链:将所有不是重链的边可相连的相连,形成的集合与重链的集合的并集是一棵树上链的集合。
特别的,对于叶子节点,有一条起始于该节点结束与该节点的链。
链头:一个点是链头当且仅当他在自身所在的链上是深度最低的点
链的深度:链的深度指链上深度最低的点(链头)的深度。
考虑如下性质:
设\(size(u)\)为\(u\)的子树大小。那么对于一个节点的重儿子\(u\)和轻儿子\(v\),一定满足
\(size(v)~\leq~size(u)\)。
根据上面的性质可以证明:
对于一个点到根节点的简单路径中,重链条数不超过\(logn\)条,轻链条数不超过\(logn\)条。
那么考虑根据这个性质,如果将每条链上的时间戳作为定为连续的,那么分区间查询与修改的次数与\(logn\)同阶。加上线段树的复杂度,每次操作的复杂度会成为\(O(log^2n)\),对于操作次数与\(n\)同阶的题目,操作的题目的总复杂度会降低至\(O(nlog^2n)\)。
考虑实现
首先显然可以通过一次dfs处理处每个点的子树大小,以及重儿子。同时习惯上我在第一次dfs时递推出每个点的深度和父节点,以备查询应用。
void DFS(ci u,ci ft) {
sz[u]=1;
deepth[u]=deepth[ft]+1;
fa[u]=ft;
for(rg int i=hd[u];i;i=edge[i].nxt) {
int &to=edge[i].to;
if(sz[to]) continue;
DFS(to,u);sz[u]+=sz[to];
if(sz[to]>sz[son[u]]) son[u]=to;
}
}
然后考虑对每个点打上时间戳。同一条链上时间戳一定是需要连续的。所以对于每个点先向他的重儿子递归打上时间戳,然后递归轻儿子。这样保证了对于每条链的时间戳都是连续的。下面使用\(top\)数组记录每个节点所在链的链头(即深度最低的节点)的节点编号。使用\(dfn\)数组记录每个点的时间戳。这样在查询的时候查询对应节点\(u\)到链头的区间就是\([dfn_{top_u},dfn_u]\)。同时使用\(remap\)数组将时间戳映射回对应的节点。
void dfs(ci u,ci tp) {
if(!u) return;
top[u]=tp;
dfn[u]=++vistime;
remap[vistime]=u;
dfs(son[u],tp);
for(rg int i=hd[u];i;i=edge[i].nxt) {
int &to=edge[i].to;
if(dfn[to]) continue;
if(to == son[u]) continue;
dfs(to,to);
}
}
考虑查询。
对于两点不在一条链上的情况,显然他们的公共祖先在他们深度较深的链上方。那么可以直接查询该节点到链头,然后跳到链头的父节点。在同一个链时,直接在链上查询即可。
具体代码如下:
int ask_l(int a,int b) {
int _ans=0;
while(top[a] != top[b]) {
if(deepth[top[a]] < deepth[top[b]]) mswap(a,b);
_ans=(0ll+_ans+ask(1,n,1,dfn[top[a]],dfn[a]))%MOD;
a=fa[top[a]];
}
if(deepth[a] < deepth[b]) mswap(a,b);
_ans=(0ll+_ans+ask(1,n,1,dfn[b],dfn[a]))%MOD;
return _ans;
}
考虑对子树的操作:
因为一棵树的子树显然编号是连续的。所以以\(u\)为根的子树在区间上对应的区间是\(dfn[u],dfn[u]+sz[u]-1\)。修改与查询次数都收\(O(1)\)。
这样,对一棵树进行剖分,进行链上信息查询的复杂度就被降低为\(O(mlog^2n)\),其中\(m\)代表操作次数。
Example
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
Hint
对于100%的数据: $ N \leq {10}^5, M \leq {10}^5 $
( 其实,纯随机生成的树LCA+暴力是能过的,可是,你觉得可能是纯随机的么233 )
typedef long long int ll;
namespace IO {
char buf[90];
}
template
inline void qr(T &x) {
char ch=getchar(),lst=' ';
while(ch>'9'||ch<'0') lst=ch,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(lst=='-') x=-x;
}
template
inline void write(T x,const char aft,const bool pt) {
if(x<0) x=-x,putchar('-');
int top=0;
do {
IO::buf[++top]=x%10+'0';
x/=10;
} while(x);
while(top) putchar(IO::buf[top--]);
if(pt) putchar(aft);
}
template
inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
template
inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
template
inline T mabs(const T a) {if(a<0) return -a;return a;}
template
inline void mswap(T &a,T &b) {
T temp=a;a=b;b=temp;
}
const int maxn = 100010;
const int maxm = 200010;
const int maxt = 400010;
struct Edge {
int to,nxt;
};
Edge edge[maxm];int hd[maxn],ecnt;
inline void cont(ci from,ci to) {
Edge &e=edge[++ecnt];
e.to=to;e.nxt=hd[from];hd[from]=ecnt;
}
int n,m,r,MOD,a,b,c,vistime;
int MU[maxn],sz[maxn],son[maxn],dfn[maxn],remap[maxn],fa[maxn],top[maxn],deepth[maxn],frog[maxt],lazy[maxt];
void DFS(ci,ci);
void dfs(ci,ci);
int ask_t(ci);
void change_t(ci,ci);
int ask_l(int,int);
void build(ci,ci,ci);
void Free(ci,ci,ci,ci,ci,ci);
void change_l(int,int,ci);
int ask(ci,ci,ci,ci,ci);
void change(ci,ci,ci,ci,ci,ci);
int main() {
qr(n);qr(m);qr(r);qr(MOD);deepth[0]=-1;
for(rg int i=1;i<=n;++i) qr(MU[i]);
for(rg int i=1;i<n;++i) {
a=b=0;qr(a);qr(b);
cont(a,b);cont(b,a);
}
DFS(r,0);dfs(r,r);
build(1,n,1);
while(m--) {
a=0;qr(a);
switch(a) {
case 1:
a=b=c=0;qr(a);qr(b);qr(c);
change_l(a,b,c);break;
case 2:
a=b=0;qr(a);qr(b);write(ask_l(a,b),'\n',true);
break;
case 3:
a=b=0;qr(a);qr(b);
change_t(a,b);break;
case 4:
a=0;qr(a);
write(ask_t(a),'\n',true);
break;
}
}
return 0;
}
void DFS(ci u,ci ft) {
sz[u]=1;
deepth[u]=deepth[ft]+1;
fa[u]=ft;
for(rg int i=hd[u];i;i=edge[i].nxt) {
int &to=edge[i].to;
if(sz[to]) continue;
DFS(to,u);sz[u]+=sz[to];
if(sz[to]>sz[son[u]]) son[u]=to;
}
}
void dfs(ci u,ci tp) {
if(!u) return;
top[u]=tp;
dfn[u]=++vistime;
remap[vistime]=u;
dfs(son[u],tp);
for(rg int i=hd[u];i;i=edge[i].nxt) {
int &to=edge[i].to;
if(dfn[to]) continue;
if(to == son[u]) continue;
dfs(to,to);
}
}
void build(ci l,ci r,ci p) {
if(l > r) return;
if(l == r) {frog[p]=MU[remap[l]];return;}
int mid=(l+r)>>1,dp=p<<1,ddp=dp|1;
build(l,mid,dp);build(mid+1,r,ddp);
frog[p]=(frog[dp]+frog[ddp])%MOD;
}
int ask_l(int a,int b) {
int _ans=0;
while(top[a] != top[b]) {
if(deepth[top[a]] < deepth[top[b]]) mswap(a,b);
_ans=(0ll+_ans+ask(1,n,1,dfn[top[a]],dfn[a]))%MOD;
a=fa[top[a]];
}
if(deepth[a] < deepth[b]) mswap(a,b);
_ans=(0ll+_ans+ask(1,n,1,dfn[b],dfn[a]))%MOD;
return _ans;
}
inline int ask_t(ci u) {
int r=dfn[u]+sz[u]-1;
return ask(1,n,1,dfn[u],r);
}
void change_l(int a,int b,ci v) {
while(top[a] != top[b]) {
if(deepth[top[a]] < deepth[top[b]]) mswap(a,b);
change(1,n,1,dfn[top[a]],dfn[a],v);
a=fa[top[a]];
}
if(deepth[a] < deepth[b]) mswap(a,b);
change(1,n,1,dfn[b],dfn[a],v);
}
void change_t(ci u,ci v) {
int r=dfn[u]+sz[u]-1;
change(1,n,1,dfn[u],r,v);
}
void change(ci l,ci r,ci p,ci aiml,ci aimr,ci v) {
if(l > r) return;
if((l > aimr) || (r < aiml)) return;
if((l >= aiml) && (r <= aimr)) {lazy[p]+=v;frog[p]=((1ll(r-l+1)%MOD)v+frog[p])%MOD;return;}
int mid=(l+r)>>1,dp=p<<1,ddp=dp|1;
Free(l,r,mid,p,dp,ddp);
change(l,mid,dp,aiml,aimr,v);change(mid+1,r,ddp,aiml,aimr,v);
frog[p]=(frog[dp]+frog[ddp])%MOD;
}
void Free(ci l,ci r,ci mid,ci p,ci dp,ci ddp) {
frog[dp]=(1ll(mid-l+1)%MODlazy[p]+frog[dp])%MOD;
frog[ddp]=(1ll(r-mid)%MODlazy[p]+frog[ddp])%MOD;
lazy[dp]=(lazy[dp]+lazy[p])%MOD;
lazy[ddp]=(lazy[ddp]+lazy[p])%MOD;
lazy[p]=0;
}
int ask(ci l,ci r,ci p,ci aiml,ci aimr) {
if(l > r) return 0;
if((l > aimr) || (r < aiml)) return 0;
if((l >= aiml) && (r <= aimr)) return frog[p];
int mid=(l+r)>>1,dp=p<<1,ddp=dp|1;
Free(l,r,mid,p,dp,ddp);
return (ask(l,mid,dp,aiml,aimr)+ask(mid+1,r,ddp,aiml,aimr))%MOD;
}