最近临近NOIP,想想自己还有啥NOIP考的东东不会,后来发现,树链剖分算是一个很容易想到的暴力,但其实在NOIP中考的东东如果能用树剖,基本上都能用倍增LCA求解,而且倍增复杂度更优
进入正题
先引入一道例题:
题目大意:给定一棵树,有m次操作,每次操作有两种可能
1
1
将到
v
v
之间的节点去全部加上一个数
2
2
询问号点的权值
这一看就是线段树模板题啊,但是变成了树上操作,那么问题就只剩下如何维护树上线段树,对于这个问题,好像仍没有什么高效解法,只剩下一个较为高效的方法,名曰:树链剖分
顾名思义,树链剖分就是一个将树剖分成链的操作,由于线段树中只能维护线性结构,但树型结构是由多条链组成的,所以我们将一棵树拆成数条链,并将这些链以任意顺序放进线段树中(等会儿会提到为啥是任意顺序),再维护线段树即可
先提提怎么更改 u u 到的路径,也是把路径拆成与树拆成的链一样的链(也就是树中拆了哪些链,路径拆成哪些链),再对每条链进行线段树上修改
但随意拆树会导致链数量较多,维护起来较为分散,导致会进行较多次数的线段树区间修改,所以我们使用神奇的(神奇海螺)重边
下面引入概念:
重儿子: 当前节点的所有儿子节点中,以重儿子节点为子树的节点数最多
重边: 所有节点与其重儿子连成的边
重链: 以一个点开始,递归找到的所有重边组成的链
——摘自《神奇海螺百科》
上面的概念刚接触可能有一点点点点点点不好理解,多看几遍就好了
这样的结果就是所有的链的长度都尽可能地相近(迷?)
反正这样将路径分解出来的链尽可能少
这样我们就需要将重链求出来,将重链求出来就算预处理完毕了
至于求重链的方法,需要俩DFS,第一遍求重儿子重边,第二遍求重链
下面是两次DFS的代码:(求重儿子重边)
void dfs(int x,int father,int deep){
depth[x]=deep; //记录深度
dad[x]=father; //记录粑粑
siz[x]=1; //记录以当前节点为根节点的子树节点数量
for(rg int i=head[x];i;i=a[i].nxt)if(a[i].v!=father){
dfs(a[i].v,x,deep+1);
siz[x]+=siz[a[i].v];
if(son[x]==-1||siz[a[i].v]>siz[son[x]])son[x]=a[i].v; //找重儿子
}
}
下面是第二次DFS:(求重链)
void dfs1(int x,int tp){ //tp是当前节点所在重链的最上层节点
top[x]=tp;
tip[x]=++tot; //记录当前节点在线段树中的位置 :树节点->线段树
rak[tip[x]]=x; //记录线段树中相应位置的节点 :线段树->树节点
if(son[x]!=-1)
dfs1(son[x],tp); //先处理当前的重链
for(rg int i=head[x];i;i=a[i].nxt)
if(a[i].v!=dad[x]&&a[i].v!=son[x])
dfs1(a[i].v,a[i].v);//再处理以当前节点的非重儿子为顶的重链(其他重链)
}
现在可以回答前面的问题了,看完代码,发现链与链之间并没有什么关联,而且在线段树上的查询修改啥的都不会跨链,所以链之间在线段树上的相对位置是随意的,只要保证一条链中元素相对位置对应即可
然后就是如何将路径拆分了
由于同一条重链在线段树中的位置都是连续的,所以对于从 u u 到之间经过的所有重链我们都要进行一次线段树上区间更改
下面讨论如何修改路径
若 x x 和在同一重链中,即可直接在线段树中维护(一条重链在线段树中的位置是连续的)
至于如何快速拆分路径,我们就可以用到刚才第二次DFS时求出的 top t o p 数组加速拆分
至于如何拆分,就是若俩点不在一条重链中,将其中一个点拉到当前重链顶节点的爹爹
(比喻起来重链就是高速公路,非重链就是国道)
作为OIER看代码会更容易理解:
void add(int x,int y,int C){
while(top[x]!=top[y]){ //将俩节点提到同一重链中
if(depth[top[x]]<depth[top[y]])swap(x,y);
update(tip[top[x]],tip[x],C,1,n,1); //将当前经过的的重链进行线段树维护
x=dad[top[x]]; //神奇地加速寻找,直接提到重链顶端的爹爹,特别像高速公路
}
if(depth[x]>depth[y])swap(x,y);
update(tip[x],tip[y],C,1,n,1); //当前情况下俩点在同一重链中,直接线段树维护
}
看完这段代码,发现一条路径会被分为 log2n l o g 2 n 条重链(至于为什么,我也不会证,只能感性的想想,如果实在不能理解,为什么不问问神奇海螺呢?)神奇海螺表示:问度娘
然而每次对重链的线段树维护的复杂度是 O(log2n) O ( l o g 2 n ) ,所以m次操作的复杂度为 O(mlog22n) O ( m l o g 2 2 n ) ,再加上之前的预处理和线段树建树,总复杂度是 O(n+mlog22n+nlog2n) O ( n + m l o g 2 2 n + n l o g 2 n )
然鹅比LCA倍增的复杂度
O(nlog2n+mlog2n)
O
(
n
l
o
g
2
n
+
m
l
o
g
2
n
)
的复杂度高到不知道哪里去了,所以我还是选择倍增LCA (话说我为甚么要学树链剖分)
看模板很容易理解:(以下摘)
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<cstdlib>
#include<climits>
using namespace std;
#define cl(x) memset(x,0,sizeof(x))
#define rg register
#define cl1(x) memset(x,-1,sizeof(x))
template <typename _Tp> inline void read(_Tp &x){
char c11=getchar();x=0;bool booo=0;
while(c11<'0'||c11>'9'){if(c11=='-')booo=1;c11=getchar();}
while(c11>='0'&&c11<='9'){x=x*10+c11-'0';c11=getchar();}
if(booo)x=-x;
}
const int maxn=50050;
int n,q;
int siz[maxn],depth[maxn],dad[maxn],son[maxn],tot=0,s[maxn];
int top[maxn],tip[maxn],rak[maxn],sum[maxn<<2],lazy[maxn<<2];
struct node{int v,nxt;}a[maxn<<1];
int head[maxn],p=0;
inline void add_edge(int,int);
void init();
void dfs(int x,int father,int deep){
depth[x]=deep;
dad[x]=father;
siz[x]=1;
for(rg int i=head[x];i;i=a[i].nxt)if(a[i].v!=father){
dfs(a[i].v,x,deep+1);
siz[x]+=siz[a[i].v];
if(son[x]==-1||siz[a[i].v]>siz[son[x]])son[x]=a[i].v;
}
}
void dfs1(int x,int tp){
top[x]=tp;
tip[x]=++tot;
rak[tip[x]]=x;
if(son[x]!=-1)
dfs1(son[x],tp);
for(rg int i=head[x];i;i=a[i].nxt)
if(a[i].v!=dad[x]&&a[i].v!=son[x])
dfs1(a[i].v,a[i].v);
}
inline void pushup(int x){sum[x]=max(sum[x<<1],sum[x<<1|1]);}
inline void pushdown(int x,int len){
if(lazy[x]){
lazy[x<<1]+=lazy[x];
lazy[x<<1|1]+=lazy[x];
sum[x<<1]+=(len-(len>>1))*lazy[x];
sum[x<<1|1]+=(len>>1)*lazy[x];
lazy[x]=0;
}
}
void build(int l,int r,int x){
lazy[x]=0;
if(l==r){sum[x]=s[rak[l]];return ;}
int mid=(l+r)>>1;
build(l,mid,x<<1);
build(mid+1,r,x<<1|1);
pushup(x);
}
int query(int l,int r,int x,int X){
if(l==r)return sum[x];
pushdown(x,r-l+1);
int mid=(l+r)>>1;
int temp=0;
if(X<=mid) temp=query(l,mid,x<<1,X);
else temp=query(mid+1,r,x<<1|1,X);
pushup(x);
return temp;
}
void update(int L,int R,int ADD,int l,int r,int x){
if(L<=l && r<=R){
lazy[x]+=ADD;
sum[x]+=ADD*(r-l+1);
return ;
}
pushdown(x,r-l+1);
int mid=(l+r)>>1;
if(L<=mid) update(L,R,ADD,l,mid,x<<1);
if(mid<R) update(L,R,ADD,mid+1,r,x<<1|1);
pushup(x);
}
void add(int x,int y,int C){
while(top[x]!=top[y]){
if(depth[top[x]]<depth[top[y]])swap(x,y);
update(tip[top[x]],tip[x],C,1,n,1);
x=dad[top[x]];
}
if(depth[x]>depth[y])swap(x,y);
update(tip[x],tip[y],C,1,n,1);
}
int main(){
while(~scanf("%d",&n)){
init();
dfs(1,0,0);
dfs1(1,1);
build(1,n,1);
char ps[10];
while(q--){
scanf("%s",ps);
int A,B,C;
if(ps[0]=='Q'){read(A);printf("%d\n",query(1,n,1,tip[A]));}
else {read(A);read(B);read(C);if(ps[0]=='D')C=-C;add(A,B,C);}
}
}
return 0;
}
void init(){
read(n);++n;read(q);tot=0,p=0;
cl(head);cl1(son);cl(lazy);
for(rg int i=1;i<=n;++i)read(s[i]);
int A,B;
for(rg int i=1;i<n;++i){
read(A);read(B);
add_edge(A,B);add_edge(B,A);
}
}
inline void add_edge(int u,int v){
a[++p].v=v;
a[p].nxt=head[u];
head[u]=p;
}