此文用来帮助那些像我一样在这方面迷茫的人
我们知道,如果给你一个数组,我们可以利用线段树在log2(n)的时间内完成区间或单点的查询与修改,但是,如果给你一棵树,要求点到点间对边权或点权的查询与修改,要怎么做呢?
很容易想到树上求最近公共祖先,然后暴力修改查询,但是那样做的复杂度会很高(log2(n)+n)左右,明显无法满足要求,那么能不能把树上的点或边放到线段树等数据结构中,利用数据结构的优点完成这一操作呢?
于是我们引入了树链剖分,,,(剖读(pou) = =
我对树链剖分的理解就是把树进行分割,把最多的点放在一起,做到把树变成链。
首先介绍几个概念:
重儿子:对于点u,她的子节点v中最大的是重儿子,最大的意思是该子树所包含的节点数最多
轻儿子:不是重儿子的儿子
重边:节点u与重儿子v的连边
轻边:节点u与轻儿子的连边
重链:重边连成的链
我们要实现她,可能会用到以下几个数组:
siz[i]:节点i为根的子树所包含的节点个数,fa[i]:i的爸爸,son[i]:i的重儿子编号,dep[i]:i的深度(根节点深度为1);
top[i]:节点i所处重链的顶端节点(如果她与父节点连边为轻边,则top[i]=i),p[i]:节点i在数据结构中所处的位置。
算法流程:先进行一遍dfs,可以求出siz,fa,son,dep这个都会把。
再进行一遍dfs,求出top,同时求出p具体做法:
如果有重儿子,top[son[x]]=top[x],p[son[x]]=++Index,同时对他dfs
如果没有,则top[v]=v,pos[v]=++Index,也对他dfs
然后,再把每个点插入到线段树中,就完成了建树。
那么怎么进行点到点的操作呢?
观察到因为重链上的点一定是在线段树中连续的,所以如果要对同一重链上两点进行查询,可以在O(log2(n))内用线段树完成。
那么如果不在同一重链呢?
然后就是玄学所在....
设我们要求u,v间的最大值,那么设uf=top[u],vf=top[v];如果(vf!=uf) 说明两点不在同一重链,那么找出uf和vf中深度大的,设记为u,uf,显然,x和y在线段树中的连续位置,求出两点间信息,令u=fa[uf],uf=top[u,重复上述操作,直到两点处于同一重链。
现在就好办了,再次找出深度大的点,然后线段树就好了。
(虽然并不会随便注意到。。。 为什么这样做的时间复杂度就会小呢?我们可以注意到(虽然并不会直接注意到。。。:任意一个点到根节点上的轻边与重链条数之和一定<=log2(n),也就是说一个点到根节点的操作至多只会进行log2(n)次log2(n)的线段树查询,复杂度上限即二者乘积。
如果只看文字无法理解,大家可以尝试画图,也可以看一下下面的代码,相信应该会很快掌握。
例题:BZOJ1036,树链剖分模板题。
题意:一棵树,支持单点修改,点到点求最大值,求和操作。
运用上述树链剖分知识,直接上代码(为什么我的代码总是那么长啊QAQ
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long ll;
const int maxn = 30010;
using namespace std;
int n,m;
int first[maxn];
struct edg
{
int next;
int to;
}e[maxn<<1];
ll val[maxn];
int top[maxn],fa[maxn],dep[maxn];
int son[maxn],p[maxn],siz[maxn];
int e_sum;
int cnt;
struct sg_tree
{
ll mmax;
ll sum;
}node[maxn<<2];
void add_edg(int x,int y)
{
e_sum++;
e[e_sum].next=first[x];
first[x]=e_sum;
e[e_sum].to=y;
}
void updata(int rt)
{
node[rt].mmax=max(node[rt<<1].mmax,node[rt<<1|1].mmax);
node[rt].sum=node[rt<<1].sum+node[rt<<1|1].sum;
}
void dfs1(int x,int f,int d)
{
dep[x]=d;
siz[x]=1;
for(int i=first[x];i;i=e[i].next)
{
int w=e[i].to;
if(w!=f)
{
dfs1(w,x,d+1);
siz[x]+=siz[w];
if(siz[w]>siz[son[x]])
son[x]=w;
fa[w]=x;
}
}
}
void dfs2(int x,int f,int y)
{
top[x]=y;
if(son[x])
{
p[son[x]]=++cnt;
dfs2(son[x],x,top[x]);
}
for(int i=first[x];i;i=e[i].next)
{
int w=e[i].to;
if(w!=f&&w!=son[x])
{
p[w]=++cnt;
dfs2(w,x,w);
}
}
}
void insert(int l,int r,int rt,int pos,ll x)
{
if(l==r)
{
node[rt].mmax=x;
node[rt].sum=x;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) insert(lson,pos,x);
else insert(rson,pos,x);
updata(rt);
}
ll query_max(int l,int r,int rt,int left,int right)
{
if(left<=l&&r<=right) return node[rt].mmax;
int mid=(l+r)>>1;
if(right<=mid) return query_max(lson,left,right);
else if(left>mid) return query_max(rson,left,right);
else return max(query_max(lson,left,mid),query_max(rson,mid+1,right));
}
ll getmax(int x,int y)
{
int f1=top[x],f2=top[y];
ll tmp=-0x3f3f3f3f;
while(f1!=f2)
{
if(dep[f1]<dep[f2]) swap(f1,f2),swap(x,y);
tmp=max(tmp,query_max(1,n,1,p[f1],p[x]));
x=fa[f1],f1=top[x];
}
if(dep[x]<dep[y]) swap(x,y);
return max(tmp,query_max(1,n,1,p[y],p[x]));
}
ll query_sum(int l,int r,int rt,int left,int right)
{
if(left<=l&&r<=right) return node[rt].sum;
int mid=(l+r)>>1;
if(right<=mid) return query_sum(lson,left,right);
else if(left>mid) return query_sum(rson,left,right);
else return query_sum(lson,left,mid)+query_sum(rson,mid+1,right);
}
ll getsum(int x,int y)
{
int f1=top[x],f2=top[y];
ll tmp=0;
while(f1!=f2)
{
if(dep[f1]<dep[f2]) swap(f1,f2),swap(x,y);
tmp+=query_sum(1,n,1,p[f1],p[x]);
x=fa[f1],f1=top[x];
}
if(dep[x]<dep[y]) swap(x,y);
return tmp+query_sum(1,n,1,p[y],p[x]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y,z;
scanf("%d%d",&x,&y);
add_edg(x,y);
add_edg(y,x);
}
for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
dfs1(1,-1,1);
p[1]=++cnt;
dfs2(1,-1,1);
for(int i=1;i<=n;i++)
insert(1,n,1,p[i],val[i]);
scanf("%d",&m);
while(m--)
{
char c[10];
int x,y;
scanf("%s%d%d",c,&x,&y);
if(c[1]=='H') insert(1,n,1,p[x],y);
else if(c[1]=='M') printf("%lld\n",getmax(x,y));
else printf("%lld\n",getsum(x,y));
}
return 0;
}
一开始WA了一次,后来发现自己真是萌的出血,点权可能是负数,但是我最大值一开始是0,不WA才怪!
以后不管怎么样,求Max先赋-0x3f3f3f3f,求Min先赋0x3f3f3f3f !!!!!
感悟:自学真的非常重要,不能一直等着人教,花一点时间学一会总会学懂,不要怕看似困难的知识。