LCA-树链剖分

树链剖分

在树上求LCA,我们常用的方法如:倍增法,离线Tarjin,转欧拉序,以及树链剖分。

树剖与其他算法相比,时间复杂度相近,而优点是可以与线段树结合,支持修改

原理:

树剖就是将一棵树,根据子节点个数剖分成轻、重链,轻、重儿子,轻、重边。

轻、重儿子:重儿子为该点子节点中,子树体积(size)最大的,其余的为轻儿子。对于每一节点,有且只有一个重儿子。

轻、重边:重边是由重儿子与父亲节点组成的边,轻边是由轻儿子与父亲节点组成的边。

轻、重链:重链是由连续的重边组成,其余的为轻边。

同时,我们需要维护每条重链的链头,而对于一个轻儿子,其链头为本身。

在开始求解LCA之前,我们需要两次搜索,来维护以下信息:

重儿子son[i] 层数dep[i] 大小sz[i] 父亲节点fa[i] 链头top[i]

并使用连接表完成对树的存储。

第一次搜索,我们需要维护出重儿子、层数、大小、及父亲节点

 

void dfs1(int x,int f)
{
   sz[x]=1;
   dep[x]=dep[f]+1;
   for(int i=head[x];i;i=nxt[i])
  {
       int y=to[i];
       if(y==f) continue;
       dfs1(y,x);
       sz[x]+=sz[y];
       if(sz[y]>sz[son[x]]) son[x]=y;
  }
}

第二次搜索,我们需要维护链头

void dfs2(int p,int tp)
{
   top[p]=tp;
   if(son[p]) dfs2(son[p],tp);
   for(int i=head[p];i;i=nxt[i])
  {
       if(to[i]!=fa[p]&&to[i]!=son[p]) dfs2(to[i],to[i]);
  }
}

这里有一个细节,我们先搜索重儿子,其原因我们会在之后的线段树中做解释。

最后,类似于倍增法,我们让两个查询点向上“跳”到其链头的父亲节点处

这种做法的优点在于,如果该链长度巨大,我们可以直接沿着重边跳上去,节省时间。

int lca(int x,int y)
{
   while(top[x]!=top[y])
  {
       if(dep[top[x]]<dep[top[y]]) swap(x,y);
       x=fa[top[x]];      
  }
   if(dep[x]>dep[y]) swap(x,y);
   return x;
}

线段树:

在我们第二次dfs中,我们可再维护出DFS序

DFS序:在进行DFS时递归每个节点的顺序,每个节点仅进入一次。

在此,区分一下欧拉序。

欧拉序:DFS时的路线,及每个节点可以多次进出。

例:

在该树中:

DFS序为:1 3 5 6 7 8 9 2 4

欧拉序为: 1 2 4 2 1 3 5 6 5 7 5 3 8 3 1 9

在上文中我们提到,要优先遍历重儿子,这样的好处是,在DFS序中,所有重链上的节点都是连续的

于是,我们便可以用线段树对这些链进行修改。

例:运用树剖进行一条路径上的区间查和

在上文LCA函数中,我们每次会向上跳一条重链,此时,我们便可用DFS序和线段树进行区间的求和。

int query_s(int l,int r,int x,int y,int p)
{
   int mid=(l+r)>>1;
   if(x<=l&&y>=r) return s[p];
   int ret=0;
   if(x<=mid) ret+=query_s(l,mid,x,y,lson);
   if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
   return ret;    
}
int qs(int x,int y)
{
   int ret=0;
   while(top[x]!=top[y])
  {
       if(dep[top[x]]<dep[top[y]]) swap(x,y);
       ret+=query_s(1,n,idx[top[x]],idx[x],1);
       x=fa[top[x]];
  }
   if(dep[x]>dep[y]) swap(x,y);
   ret+=query_s(1,n,idx[x],idx[y],1);
   return ret;
}

例题:洛谷P2590 [ZJOI2008]树的统计

题目传送门

本题是一道裸的树剖,需要单点修改、区间求和、以及区间求最大值。

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 30010
#define lson p<<1
#define rson p<<1|1
int head[maxn],to[maxn<<1],nxt[maxn<<1];
int top[maxn],fa[maxn],son[maxn],dep[maxn],sz[maxn],idx[maxn];
int n;
int v[maxn];
int num=0,tot=0,cnt=0;
int s[maxn<<2],m[maxn<<2];
#define inf -100000000
#define MADE return
#define BY 0
#define JZW ;
void ct(int l,int r,int p)
{
  int mid=(l+r)>>1;
  printf("%d to %d m=%d s=%d ",l,r,m[p],s[p]);
  if(l==r)return;
  ct(l,mid,lson);
  ct(mid+1,r,rson);
}
int query_m(int l,int r,int x,int y,int p)
{
  int mid=(l+r)>>1;
  if(x<=l&&y>=r)
  return m[p];
  int ret=inf;
  if(x<=mid) ret=query_m(l,mid,x,y,lson);
  if(y>mid) ret=max(ret,query_m(mid+1,r,x,y,rson));
  return ret;
}
int query_s(int l,int r,int x,int y,int p)
{
  int mid=(l+r)>>1;
  if(x<=l&&y>=r) return s[p];
  int ret=0;
  if(x<=mid) ret+=query_s(l,mid,x,y,lson);
  if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
  return ret;    
}
void build(int l,int r,int p)
{
  int mid=(l+r)>>1;
  if(l==r)
  {
      s[p]=m[p]=v[l];
      return;
  }
  build(l,mid,lson);
  build(mid+1,r,rson);
}
void add(int x,int y)
{
  to[++tot]=y;
  nxt[tot]=head[x];
  head[x]=tot;
}
void dfs1(int x,int f)
{
  fa[x]=f;
  sz[x]=1;
  dep[x]=dep[f]+1;
  for(int i=head[x];i;i=nxt[i])
  {
      int y=to[i];
      if(y==f) continue;
      dfs1(y,x);
      sz[x]+=sz[y];
      if(sz[son[x]]<sz[y]) son[x]=y;
  }
}
void dfs2(int p,int tp)
{
  top[p]=tp;
  idx[p]=++num;
  if(son[p]) dfs2(son[p],tp);
  for(int i=head[p];i;i=nxt[i])
  {
      int y=to[i];
      if(y!=fa[p]&&y!=son[p])
      dfs2(y,y);
  }
}
void update(int l,int r,int x,int v,int p)
{
  int mid=(l+r)>>1;
  if(l==r)
  {
      m[p]=v;
      s[p]=v;
      return;
  }
  if(x<=mid) update(l,mid,x,v,lson);
  else update(mid+1,r,x,v,rson);
  m[p]=max(m[lson],m[rson]);
  s[p]=s[lson]+s[rson];
}
int qs(int x,int y)
{
  int ret=0;
  while(top[x]!=top[y])
  {
      if(dep[top[x]]<dep[top[y]]) swap(x,y);
      ret+=query_s(1,n,idx[top[x]],idx[x],1);

      x=fa[top[x]];
  }
  if(dep[x]>dep[y]) swap(x,y);

  ret+=query_s(1,n,idx[x],idx[y],1);
  return ret;
}
int qm(int x,int y)
{
  int ret=inf;
  while(top[x]!=top[y])
  {
      if(dep[top[x]]<dep[top[y]]) swap(x,y);
      ret=max(ret,query_m(1,n,idx[top[x]],idx[x],1));

      x=fa[top[x]];
  }
  if(dep[x]>dep[y]) swap(x,y);
  ret=max(ret,query_m(1,n,idx[x],idx[y],1));

  return ret;
}
int main()
{
  scanf("%d",&n);
  for(int i=1;i<n;i++)
  {
      int x,y;
      scanf("%d%d",&x,&y);
      add(x,y);
      add(y,x);
  }
  dfs1(1,0);
  dfs2(1,1);
  for(int i=1;i<=n;i++)
  {
      int v;
      scanf("%d",&v);
      update(1,n,idx[i],v,1);
  }

  int q;
  scanf("%d",&q);
int d=0;
  while(q--)
  {
      char op[10];
      scanf("%s",op);
      if(op[0]=='C')
      {
          int x,y;
          scanf("%d%d",&x,&y);
          update(1,n,idx[x],y,1);
      }
      if(op[1]=='M')
      {
          int x,y;
          scanf("%d%d",&x,&y);
          if(d==1) printf(" ");
          printf("%d",qm(x,y));
          d=1;
      }
      if(op[1]=='S')
      {
          int x,y;
          scanf("%d%d",&x,&y);
          if(d==1) printf(" ");
          printf("%d",qs(x,y));
          d=1;
      }
  }
  MADE BY JZW
}

注意:对于线段树的操作要输入对应的DFS序

原文地址:https://www.cnblogs.com/Marcelo/p/13797478.html