树链剖分,可以算是一种思想吧,因为它一般会和其它数据结构套在一起用。
之前自己在理解的时候写了个ppt,以便以后能够再忘了的时候看看。
•树链剖分入门
•
•
---------By nikelong
•遇到这样一道题:给定一棵树,每条边又一个权值,询问任意两点u,v之间路径上的权值和和极值。
•
•怎么做?
•LCA?
•
------LCA可以做,但是如果操作数极大怎么办?
•引入树链剖分
•树链:从根到叶子节点的一条路径就是一条树链
•树链剖分,“望文生义”可以知道是把树链进行剖分。
•树链,我们把它分成两类,一类是重链(不是很重,而是重边组成的链);另一类是轻链(由轻边组成)。
•分成这两类链后会有一些神奇的操作
补充一些概念:
重儿子:size[u]为v的子节点中size值最大的, 那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2:从根到某一点的路径上轻链、重链的个数 都不大于logn。
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2:从根到某一点的路径上轻链、重链的个数 都不大于logn。
•下面讲一下树链剖分的实质。
•其实质就是把一棵树分成两类边,通过有序的dfs把重链的点编号处理成连接在一起的,之后用其他的数据结构维护重链所在区间(轻链一般是单一的)。
•由定理2可以知道,这样做的时间复杂度大致是log n的。
•
本身并不是一种固定的数据结构(个人认为),主要和其他数据结构一起来优化算法。
•如何实现?
•
我们需要弄清楚我们需要些什么。
Size[u]记录以u为根节点的子树的大小:用在求重儿子
Son[u]记录u的重儿子
Fa[u]记录u的父亲
Top[u]是u所在的重链的顶端
Deep[u]是u在树的深度
W[u]是u与父亲连边在线段树中的序号
有时我们还需要求他东西
•算法实现:
•第一次dfs求出dep,fa,son,size等信息
•第二次dfs求出top(轻重链),w(边的序号)
•之后线段树维护
•
•线段树是一个很实用的东西,忘掉了快去复习
Void dfs1(int u,int f,int d)//当前u,u->f,u->dep
{
Fa[u]=f;
Dep[u]=d+1;
Size[u]=1;//默认为1,后面累加
Son[u]=0;//附一个初值,0表示没有
For(int i=head[u];~i;i=e[i].next)//遍历所有边
{ int v=e[i].v;
if(v==f)continue;//双向建边
dfs(v,u,d+1);//先处理下一层再回溯
size[u]+=size[v];//累加size
if(size[v]>size[son[u]])son[u]=v;//更新重儿子
}
}
//这一部分我们求出了很多东西,在第二次dfs的时候可以用了
Void dfs2(int u,int tp)//当前u,u的顶端是tp
{
//为了使重链的区间连续,我们先处理重链
w[u]=++len;//编号
top[u]=tp;//链顶端
if(son[u])dfs2(son[u],tp);//先处理重儿子
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].v;
if(v==fa[u]||v==son[u])continue;//处理轻儿子
dfs2(v,v);
}
}
//有了这个映射,就可以套用线段树了
•以下以spoj 375为例
•询问任意两点之间的路径上最大值,可以修改某条边
•N=10000
•定义好各种数组先跑两次dfs预处理一下。
•线段树直接上模板,这道题是单点修改、区间查询。
•这里我们需要考虑这样一件事:对于dfs中访问的边我们都打上了标号,我们是从上往下访问的,那么每条边的信息我们是存在这条边上深度较大的那一个点上,所以对于每个记录下来的整套读入边,我们需要统一哪个深度大,以便查询的时候调用它(深度大所连边)的编号.
•重点!!!!
•
•树链剖分套用的重点在于查询。
•给定这样的数,红色表示重链
•询问两个矩形之间路径上最小值
•令左边的u,右边v
•得到f1=top[u],f2=top[v]
•很明显dep[f1]>dep[f2]
•我们先处理u和f1,计算[f1,u]上的信息,然后
•u=fa[f1],f1=top[u]
•第二次,f1、f2重合了,u=f1=f2
•然后更新一下[u,v]这一段就好了
•实现:
•void find(int u,int v)
•{
• int f1=top[u],f2=top[v];
• while(f1!=f2)
• {
• if(dep[f1]<dep[f2]){swap(u,v);swap(f1,f2);}
• ans=max(ans,query(1,w[f1],w[u]));
• u=fa[f1];f1=top[u];
• }//没在一条链上
• if(u==v)return ans;//使u在上面,处理u-v(son[u]算一条边)
• if(dep[u]>dep[v])swap(u,v);
• ans=max(ans,query(1,w[son[u]],w[v]));
•Printf(“%d\n”,ans);
•}
实现:
void find(int u,int v)
{
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2]){swap(u,v);swap(f1,f2);}
ans=max(ans,query(1,w[f1],w[u]));
u=fa[f1];f1=top[u];
}//没在一条链上
if(u==v)return ans;//使u在上面,处理u-v(son[u]算一条边)
if(dep[u]>dep[v])swap(u,v);
ans=max(ans,query(1,w[son[u]],w[v]));
Printf(“%d\n”,ans);
}
•大概就这么多了,记得存的边的变化以方便查询。
•
•
•Thanks~
另附spoj 375 AC代码(仅供参考,因为写的比较水,代码有点长):
#include<cstdio>
#include<queue>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#define maxn 10000+20
using namespace std;
int n;
int size[maxn];//以u这个点为根的子树总大小
int deep[maxn];//u的深度
int top[maxn];//u这个点的所在链的顶端
int fa[maxn];//u的父亲
int w[maxn];//与其父亲的连边在线段树中的位置
int son[maxn];//u的重儿子
int len;//区间长度
struct node
{
int u,v,w,next;
}e[4*maxn];
int d[maxn][3];
int head[maxn];
int val[maxn];
int k=1;
void add(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}
void pre_dfs(int u,int pre,int dep)
{
fa[u]=pre;
deep[u]=dep;
size[u]=1;
son[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
if(e[i].v!=pre)
{
pre_dfs(e[i].v,u,dep+1);
size[u]+=size[e[i].v];
if(size[e[i].v]>size[son[u]])son[u]=e[i].v;
}
}
}//这一部分我们处理出了fa[u],size[u],dep[u],son[u].
//第二个dfs我们需要求出每条边在线段树中的位置
void dfs(int u,int tp)//u,u->top
{
w[u]=++len;
top[u]=tp;
if(son[u]!=0)dfs(son[u],tp);
for(int i=head[u];i!=-1;i=e[i].next)
{
if(e[i].v!=fa[u]&&e[i].v!=son[u])
{
dfs(e[i].v,e[i].v);//轻链
}
}
}
struct Node
{
int Max,l,r;
}T[4*maxn];
void pushup(int x)
{
T[x].Max=max(T[x*2].Max,T[x*2+1].Max);
}
void build(int u,int l,int r)
{
T[u].l=l;
T[u].r=r;
if(l==r)
{
T[u].Max=val[l];
return ;
}
int mid=(l+r)/2;
build(u*2,l,mid);
build(u*2+1,mid+1,r);
pushup(u);
}
void updata(int u,int pos,int v)
{
if(T[u].l==T[u].r)
{
T[u].Max=v;
return ;
}
int mid=(T[u].l+T[u].r)/2;
if(pos<=mid)updata(u*2,pos,v);
else updata(u*2+1,pos,v);
pushup(u);
}
int query(int u,int l,int r)
{
//查询l,r最大值
if(T[u].l>=l&&T[u].r<=r)return T[u].Max;
int mid=(T[u].l+T[u].r)/2;
int ans=0;
if(l<=mid)ans=max(ans,query(u*2,l,r));
if(r>mid)ans=max(ans,query(u*2+1,l,r));
return ans;
}
int find(int u,int v)//查询l,r的最大值
{
int f1=top[u],f2=top[v];
int ans=0;
while(f1!=f2)
{
if(deep[f1]<deep[f2])
{
swap(f1,f2);
swap(u,v);
}
ans=max(ans,query(1,w[f1],w[u]));//
u=fa[f1];
f1=top[u];
}
if(u==v)return ans;
if(deep[u]>deep[v])
{
swap(u,v);
}
return max(ans,query(1,w[son[u]],w[v]));
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
k=1;
memset(head,-1,sizeof(head));
scanf("%d",&n);
len=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&d[i][0],&d[i][1],&d[i][2]);
add(d[i][0],d[i][1],d[i][2]);
add(d[i][1],d[i][0],d[i][2]);
}
pre_dfs(1,0,0);//u,u->f,u->dep
dfs(1,1);
for(int i=1;i<n;i++)
{
if(deep[d[i][0]]>deep[d[i][1]])swap(d[i][0],d[i][1]);;
val[w[d[i][1]]]=d[i][2];
}
build(1,1,len);
char s[100];
while(scanf("%s",s)!=EOF)
{
if(s[0]=='D')break;
int l,r;
scanf("%d%d",&l,&r);
if(s[0]=='Q')
{
printf("%d\n",find(l,r));
}
else
{
updata(1,w[d[l][1]],r);
}
}
}
return 0;
}