浅谈树链剖分(上)
P_Bisector · · 个人记录
本文极其适合包括刚刚学完树的定义的OIer在内的OIer。是人都能看懂。
例题:
P3379 LCA
虽然说树链剖分和LCA没啥关系,正式比赛时大家也多用倍增算法为主,但是在这里算是一个引子吧。
LCA,最近公共祖先,即两个点之间深度最大最亲民的公共祖先。比如这么一棵树:
father[2]=4
father[1]=4
father[3]=1
father[5]=1
(father表示一个节点的父亲节点编号。)我们假设
int LCA(int a,int b){
while(a!=b){
if(de[a]<de[b]){//将a设为深度较小的那个
swap(a,b);
}
a=fa[a];//a往上跳
}
return a;
}
这个思路很显然不是最优的。事实上,它非常烂。
它的时间复杂度最坏是
code:
void dfs(int x,int tp){//x表示当前dfs到的节点,tp表示该节点所在链的顶端节点编号
de[x]=de[fa[x]]+1;
top[x]=tp;
int flag=0;
for(int i=0;i<g[x].size();i++){
if(g[x][i]!=fa[x]){
int v=g[x][i];
fa[v]=x;
if(flag==1){//不是第一个点
dfs(v,v);//从这个点开始新建一条链
}else{//是第一个点
flag=1;
dfs(v,tp);//和原来的节点并入同一条链
}
}
}
}
int LCA(int a,int b){
while(top[a]!=top[b]){//只要不在同一条链上
if(de[top[a]]<de[top[b]]){//注意!比较所在链顶部节点的深度
swap(a,b);
}
a=fa[top[a]];//先跳一条链,再跳一条边
}
if(de[a]<de[b])swap(a,b);//返回其深度较小的
return b;
}
这个代码时间复杂度仍然是
root=1
father[2]=1
father[3]=1
father[4]=3
father[5]=3
father[6]=5
father[7]=5
…………
father[2*i]=2*i-1
father[2*i+1]=2*i-1
对于以上情况,我们会惊奇的发现,每一条链长度只有2!如果我们从节点
假设跳跃前节点为
AC code:
#include<iostream>
#include<vector>
using namespace std;
vector<int>g[500005];
int de[500005],fa[500005],top[500005],sze[500005],son[500005];
void dfs(int x){
de[x]=de[fa[x]]+1;
sze[x]=1;//注意!节点本身也算入
for(int i=0;i<g[x].size();i++){
if(g[x][i]!=fa[x]){
int v=g[x][i];
fa[v]=x;
dfs(v);
sze[x]+=sze[v];//记录子树大小
if(sze[v]>sze[son[x]]){
son[x]=v;//处理重儿子
}
}
}
}
void dfs2(int x,int tp){
if(x==0)return;//防止重儿子为0
top[x]=tp;
dfs2(son[x],tp);//计入同一链
for(int i=0;i<g[x].size();i++){
if(g[x][i]!=fa[x]&&g[x][i]!=son[x]){
int v=g[x][i];
dfs2(v,v);
}
}
}
int LCA(int a,int b){
while(top[a]!=top[b]){
if(de[top[a]]<de[top[b]]){
swap(a,b);
}
a=fa[top[a]];
}
if(de[a]<de[b])swap(a,b);
return b;
}
int main(){
int n,q,r;
cin>>n>>q>>r;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(r);
dfs2(r,r);//dfs两遍
for(int i=0;i<q;i++){
int x,y;
cin>>x>>y;
cout<<LCA(x,y)<<endl;
}
return 0;
}
时间复杂度分析:
总时间复杂度:
空间复杂度:
总结:本篇博客以LCA为引子简单介绍了树链剖分。若有不懂或不明白之处,欢迎提出。