树链剖分是维护树上路径的一种有力工具。支持以下操作:
修改:单点改权,单边改权,从u到v的简单路径上的点或边改权等等
修改权值的方式可以是加/减/乘/除一个值。
查询:单点查询,单边查询,查询从u到v的简单路径上的点或边权值之和/最大值/最小值等等
树链剖分的主要步骤:
1、找到重儿子son,顺便维护出树的fa,size等等。重儿子就是当前节点的所有儿子中size最大的那个;
2、根据重儿子划分出轻重边,当前节点向重儿子连的边是重边。重边相连形成重链,两条分开的重链被轻边相连;
3、把每条重链作为一个连续区间映射到数据结构中,轻边作为一个单位区间映射到数据结构中;
4、对于每次修改用数据结构维护,查询用数据结构查询。
常用的数据结构是线段树。
对一棵树进行树链剖分后,树的性质:
从u到根的简单路径上最多只有logn条轻边,logn条重链。
证明:设当前节点为u,轻儿子为lightson,那么size[lightson]<=size[u]/2,size[root]=n,size[lightson]>=1,那么可以发现,u到lightson的简单路径上,最多只有logn个轻儿子,那么最多只有logn条轻边。又因为重链被轻边相连,那么最多有logn条重链。
那么可以发现一次修改的时间复杂度是O(log^2n)(如果使用线段树维护)。
具体实现:
1、dfs第一次预处理size,fa,son;
2、dfs第二次预处理top(轻重链的顶端),p(当前点映射到数据结构里面的位置),fp(数据结构里面的点映射到树里面的位置),并且用dfs保证重儿子被映射到连续区间;
3、构建数据结构;
4、修改的时候,如果两个点不在一条重链上面,那么就调整深度大的点使其往它所在链的顶端走(用数据结构维护),然后通过fa数组跳到下一条链上,直到两个点在一条重链上。
5、对于在一条重链上的点,用数据结构维护。
6、查询同修改。
贴一个HDU3966的代码
树上区间修改单点查询
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 50010
using namespace std;
struct node{
int v,next;
}edge[N<<1];
struct segtree{
int l,r,val;
}tree[N<<2];
int fa[N],son[N],top[N],p[N],sz[N],v[N],head[N],dep[N],cnt,tot,n,m,k,a,b,c;
char op[3];
void addedge(int u,int v)
{
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs1(int u,int f)
{
if(u<=0)return;
int maxch=0;
sz[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v!=f)
{
fa[v]=u;
dep[v]=dep[u]+1;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>maxch)
{
maxch=sz[v];
son[u]=v;
}
}
}
}
void dfs2(int u,int t)
{
top[u]=t;
p[u]=++tot;
if(son[u]==0)return;
dfs2(son[u],t);
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v!=fa[u]&&v!=son[u])
dfs2(v,v);
}
}
void build(int x,int l,int r)
{
tree[x].l=l,tree[x].r=r;
if(l==r)return;
build(2*x,l,(l+r)/2);
build(2*x+1,(l+r)/2+1,r);
}
void change(int x,int l,int r,int v)
{
if(tree[x].l==l&&tree[x].r==r)
{
tree[x].val+=v;
return;
}
if(l>tree[x].r&&r<tree[x].l)return;
if(r<=(tree[x].l+tree[x].r)/2)change(2*x,l,r,v);
else if(l>(tree[x].l+tree[x].r)/2)change(2*x+1,l,r,v);
else
{
change(2*x,l,(tree[x].l+tree[x].r)/2,v);
change(2*x+1,(tree[x].l+tree[x].r)/2+1,r,v);
}
}
int query(int x,int u)
{
if(tree[x].l==u&&tree[x].r==u)return tree[x].val;
if(u<=(tree[x].l+tree[x].r)/2)return tree[x].val+query(2*x,u);
else if(u>(tree[x].l+tree[x].r)/2)return tree[x].val+query(2*x+1,u);
}
void changepath(int u,int v,int val)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
change(1,p[top[u]],p[u],val);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
change(1,p[v],p[u],val);
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k))
{
memset(tree,0,sizeof tree);
memset(son,0,sizeof son);
memset(top,0,sizeof top);
memset(head,-1,sizeof head);
cnt=tot=0;
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a,&b);
addedge(a,b);
addedge(b,a);
}
fa[1]=1;
dep[1]=1;
dfs1(1,0);
dfs2(1,1);
build(1,1,tot);
for(int i=1;i<=k;++i)
{
scanf("%s",op);
if(op[0]=='I')
{
scanf("%d%d%d",&a,&b,&c);
changepath(a,b,c);
}
else if(op[0]=='D')
{
scanf("%d%d%d",&a,&b,&c);
changepath(a,b,-c);
}
else
{
scanf("%d",&a);
printf("%d\n",query(1,p[a])+v[a]);
}
}
}
}