码迷,mamicode.com
首页 > 其他好文 > 详细

[知识点]树链剖分

时间:2015-07-28 18:07:24      阅读:106      评论:0      收藏:0      [点我收藏+]

标签:

// 此博文为迁移而来,写于2015年7月11日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w69l.html

 

1、前言
       树链剖分,一个高大上的名字。树链,即树上的路径,现在我们的任务是所谓的剖分。所以我们可以看出,树链剖分并不是一种单独的数据结构,不像堆,线段树等等,而是直接在一棵普通的树上处理,然而单是这一课树是并没有什么卵用的。今天先讲一个相对比较简单的情况——用一棵线段树维护主树每条边的权值
 
2、要点
       首先引入一个核心内容——重儿子
技术分享
由此可以引申出几个其他的内容。重边——非叶子节点与其重儿子所连的边;重链——由连续的重边组成的一条链。那么这个东西有什么用?先来看一道例题(也就是树链剖分与线段树维护的经典例题):
技术分享

       题目为单点修改,区间询问。单点修改不用多提,重点在于,我们如何把从节点u到节点v这条路径上的节点求出最大值以及权值和呢?先考虑一种暴力的算法——跑LCA。我们根据u和v的深度找到公共父亲节点,然后在从子节点向上跳的时候,得到最大值或是权值和(如果是修改操作,其实也是同理)。然而这终究是暴力。那么,重链在这道题中的作用就凸显出来了——为了你在跑LCA的时候往上跳得更快。
      根据最开始对重链等概念的描述,我们来看一张图:
 
技术分享

       我们需要用到线段树来维护每一条边,首先需要求得几个重要的信息——每一个节点的重儿子;当前节点所在重链的顶端(如果当前节点是没有重边相连或者本身就是顶端,则就是其本身)。在线段树维护的时候,我们为每条边的编号并不是根据读入顺序,不然就没有任何含义了!
       这个很重要——编号时,优先为其重儿子编号,直到到了叶子节点,再回溯上去为其他的儿子编号。如图所示,最开始我们记根节点的重边为1,然后一路编下去直至14号节点,回溯到4号节点,将4号节点与另一个子节点的边标号为5,以此类推。
       这样,我们的目的就开始显现了!一旦重链存在两条及以上的重边,其编号在线段树中一定是连续的。如图的1-2-3-4;10-11。这样子,由于已经求出了每条重链的顶端,每次我们跑LCA的时候,若当前节点不是重链顶端,则可以直接跳到顶端——同时,因为他们在线段树的编号是连续的,所以可以很方便的进行求值或者是修改,这一点只要会线段树的就很好理解了!
       举个例子,如果我们需要求出11号和10号点的路径上的权值之和,设初始状态x1=11,x2=10,步骤为:
              1、11的顶端为2,修改线段树中的10-11,同时x1=2;这时,dep[x1]=2,dep[x2]=3;
              2、10没有重边相连,顶端为自身,故向上找其父节点,修改线段树中的5;发现父节点所在重链顶端为1,则修改线段树中的1,同时x2=4;这时,dep[x1]=2,dep[x2]=1。(这里有一个小小的优化,即便这
       条链上是一条重边,一条轻边,也可以选择一次性向上跳完)
             3、2没有重边相连,顶端为自身,故向上找其父节点,修改线段树中的9。此时,top[x1]=top[x2],且x1=x2,循环结束。
 
3、代码
       具体实现方面的话,上文所提的重儿子,顶端,深度等等都要提前预处理出来,后面就是用线段树维护了。
----------------------------------------------------------------------------------------------------
#include<cstdio>
#include<algorithm>
#define MAXN 30005
#define INF 1<<30
using namespace std;
 
int l[MAXN],r[MAXN];
struct node
{
        int sum,max;
};
node tree[MAXN*4];     
 
struct Edge
{
        int v,next;
};
Edge edge[MAXN*2];

int head[MAXN],now,val[MAXN],link[MAXN],dep[MAXN],fa[MAXN],sonTree[MAXN],heavySon[MAXN],tot,top[MAXN],num[MAXN];     
int n,u,v,q;
 
void addEdge(int u,int v)
{
        now++;
        edge[now].v=v;
        edge[now].next=head[u];
        head[u]=now;
}
void buildTree(int now,int l,int r)
{
       if (l==r) 
       {
                tree[now].max=val[link[l]];
                tree[now].sum=val[link[l]];
                return;
       }
       int mid=(l+r)/2;
       buildTree(now*2,l,mid);
       buildTree(now*2+1,mid+1,r);
       tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
       tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}
 
void DFS1(int now,int nowFa,int nowDep)
{
       dep[now]=nowDep; fa[now]=nowFa; sonTree[now]=1;
       for (int x=head[now];x!=0;x=edge[x].next)
       {
              if (edge[x].v==nowFa) continue;
              DFS1(edge[x].v,now,nowDep+1);
              sonTree[now]+=sonTree[edge[x].v];
              if (heavySon[now]==0 || sonTree[edge[x].v]>sonTree[heavySon[now]]) heavySon[now]=edge[x].v;
       }
}
 
void DFS2(int now,int nowTop)
{
       tot++;
       top[now]=nowTop; num[now]=tot; link[tot]=now;
       if (heavySon[now]==0) return;
       DFS2(heavySon[now],nowTop);
       for (int x=head[now];x!=0;x=edge[x].next)
              if (edge[x].v!=heavySon[now] && edge[x].v!=fa[now]) DFS2(edge[x].v,edge[x].v);
}
 
void init()
{
       scanf("%d",&n);
       for (int i=1;i<=n-1;i++) 
       {
              scanf("%d %d",&u,&v);
              addEdge(u,v); addEdge(v,u);
       }
       for (int i=1;i<=n;i++) scanf("%d",&val[i]);
       DFS1(1,0,1); DFS2(1,1);
       buildTree(1,1,n);
}
 
void update1(int now,int l,int r,int loc,int delta)
{
       if (l==r)
       {
              tree[now].sum+=delta;
              tree[now].max+=delta;
              return;
       }
       int mid=(l+r)/2;
       if (loc<=mid) update1(now*2,l,mid,loc,delta);
       else update1(now*2+1,mid+1,r,loc,delta);
       tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
       tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}
 
int query1(int now,int l,int r,int ql,int qr)
{
       int ans=0;
       if (ql<=l && r<=qr) return tree[now].sum;
       int mid=(l+r)/2;
       if (ql<=mid) ans+=query1(now*2,l,mid,ql,qr);
       if (qr>mid) ans+=query1(now*2+1,mid+1,r,ql,qr);
       return ans;
}
 
int query2(int now,int l,int r,int ql,int qr)
{
       int ans=-INF;
       if (ql<=l && r<=qr) return tree[now].max;
       int mid=(l+r)/2;
       if (ql<=mid) ans=max(ans,query2(now*2,l,mid,ql,qr));
       if (qr>mid) ans=max(ans,query2(now*2+1,mid+1,r,ql,qr));
       return ans;
}
 
int getMax(int l,int r)
{
       int f1=top[l],f2=top[r],ans=-INF,nowAns;
       while (f1!=f2)
       {
              if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
             ans=max(ans,query2(1,1,n,num[f1],num[l]));
             l=fa[f1]; f1=top[l];
       }
       if (dep[l]>dep[r]) nowAns=query2(1,1,n,num[r],num[l]);
       else nowAns=query2(1,1,n,num[l],num[r]);
       ans=max(ans,nowAns);
       return ans;
}
 
int getSum(int l,int r)
{
       int f1=top[l],f2=top[r],ans=0,nowAns;
       while (f1!=f2)
       {
              if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
              ans+=query1(1,1,n,num[f1],num[l]);
              l=fa[f1]; f1=top[l];
       }
      if (dep[l]>dep[r]) nowAns=query1(1,1,n,num[r],num[l]);
      else nowAns=query1(1,1,n,num[l],num[r]);
      ans+=nowAns;
      return ans;
}
 
int main()
{
       char s[7];
       init();
       scanf("%d",&q);
       for (int i=1;i<=q;i++) 
       {
              scanf("%s %d %d",s,&u,&v);
              if (s[0]==‘C‘) { update1(1,1,n,num[u],v-val[u]); val[u]=v; }
              else if (s[1]==‘M‘) printf("%d\n",getMax(u,v));
              else printf("%d\n",getSum(u,v)); 
       }
       return 0;
}
----------------------------------------------------------------------------------------------------

[知识点]树链剖分

标签:

原文地址:http://www.cnblogs.com/jinkun113/p/4683299.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!