浅谈树链剖分(C++、算法、树结构)

关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供)

这是讲解

http://www.cnblogs.com/kuangbin/archive/2013/08/15/3259083.html

另一个是百度文库

http://wenku.baidu.com/link?url=DY8CAbwdjitIiv8XQsHmVPi--dQAqw5z6dc_6N1Plh4u5Nfc1aCADQm4oAvt4Sqe1mXSixezzK4lRxofQKMX9cNzJwYwQhpGJZuMSTI3Ktq

这是AC代码(以bzoj1036为例题)

http://www.cnblogs.com/kuangbin/archive/2013/08/15/3259083.html

但是我相信很多人有和我一样的经历,就是看各种大神的代码的时候只是膜拜他们的长度和复杂,但是大神们却并不对代码进行讲解,导致我们和多人看不懂。下来还得自己理解。

这样会花费很长的时间。。。。TAT!

下面是我对AC代码的注释,原文并没有那么多。其中没有关于线段树的知识,只是对剖分时进行了解释。对于不了解线段树的读者请自行百度。。。。。

希望能对读者有较大的帮助。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
const int MAXN = 30010;
struct Edge//E[i].to为边E[i]指向的点.
{// E[i].next为边E的上一条边(即E与E的上一条边由同一节点发出).
    int to,next;
}edge[MAXN*2];
int head[MAXN],tot;//head[j]为节点j的最后一条发出边.
int top[MAXN]; //top[v] 表示v所在的重链的顶端节点
int fa[MAXN]; //父亲节点
int deep[MAXN];//深树的度
int num[MAXN]; //num[v]表示以v为根的子树的节点数
int p[MAXN]; //p[v]表示v在线段树中的位置
int fp[MAXN];//和p数组相反
int son[MAXN];//重儿子
int pos;
void init()
{
    tot=0;pos=0;
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
}
void addedge(int u,int v)
{
    edge[++tot].to=v;edge[tot].next=head[u];head[u]=tot;
}
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son
{
    deep[u]=d;//u 深搜到的点 pre u的前驱 d 深度
    fa[u]=pre;//完成父亲数组
    num[u]=1;//u 包含的点数
    for(int i=head[u];i;i=edge[i].next){
      int v=edge[i].to;//边i的指向点
      if(v!=pre){//判断是否是i的父亲,若是跳出不是继续深搜
        dfs1(v,u,d+1);//v 深搜到的点 u v的父亲(前驱) 深度+1
        num[u]+=num[v];//父亲包含的点数=儿子包含的点数+1
        if(son[u]==-1||num[v]>num[son[u]])//son==-1时,即没有标记重儿子,直接标记
          son[u]=v;// num[v]>num[son[u]]如果v包含的节点数大于其父亲的重儿子的节点数,v变为
      }//                                                              其父亲的重儿子
    }
}
void getpos(int u,int sp)//将重儿子连成重链
{
    top[u]=sp;//sp为传递变量,即若sp不变,DFS过程中的重儿子均属于以sp为根的重链
    p[u]=pos++;//将重链的节点放在数组p中,来构建线段树
    fp[p[u]]=u;//反之
    if(son[u]==-1) return;//若son为-1,则该点为叶子节点,返回
    getpos(son[u],sp);//DFS对u的重儿子,将其加入重链中,sp不变
    for(int i=head[u];i;i=edge[i].next){//边的循环(将一条重链添加完后再对轻边进行添加)
      int v=edge[i].to;//v是边i的指向点
      if(v!=son[u]&&v!=fa[u]) getpos(v,v);//(v!=fa[u])处理v是u的父亲的情况
    }//若v是u的儿子但不是重儿子(即其属于轻边),以自己为根(修改sp)继续DFS形成新的重链
}
struct Node//线段树的点
{
    int l,r;
    int sum;
    int Max;
}segTree[MAXN*3];
void push_up(int i)
{
    segTree[i].sum=segTree[i<<1].sum+segTree[(i<<1)|1].sum;
    segTree[i].Max=max(segTree[i<<1].Max,segTree[(i<<1)|1].Max);
}
int s[MAXN];
void build(int i,int l,int r)//建线段树(不懂的看线段树的讲解)
{
    segTree[i].l=l;
    segTree[i].r=r;
    if(l==r){
      segTree[i].sum=segTree[i].Max=s[fp[l]];
      return;
    }
    int mid=(l+r)/2;
    build(i<<1,l,mid);
    build((i<<1)|1,mid+1,r);
    push_up(i);//用来储存树上的和、最大值
}
void update(int i,int k,int val)//更新线段树的第k个值为val
{
    if(segTree[i].l==k&&segTree[i].r==k){
      segTree[i].sum=segTree[i].Max=val;
      return;
    }
    int mid=(segTree[i].l+segTree[i].r)/2;
    if(k<=mid)update(i<<1,k,val);
    else update((i<<1)|1,k,val);
    push_up(i);
}
int queryMax(int i,int l,int r)//查询线段树[l,r]区间的最大值
{
    if(segTree[i].l==l&&segTree[i].r==r){
      return segTree[i].Max;
    }
    int mid=(segTree[i].l+segTree[i].r)/2;
    if(r<=mid) return queryMax(i<<1,l,r);
    else if(l > mid)return queryMax((i<<1)|1,l,r);
    else return max(queryMax(i<<1,l,mid),queryMax((i<<1)|1,mid+1,r));
}
int querySum(int i,int l,int r) //查询线段树[l,r]区间的和
{
    if(segTree[i].l==l&&segTree[i].r==r)
      return segTree[i].sum;
    int mid=(segTree[i].l+segTree[i].r)/2;
    if(r<=mid)return querySum(i<<1,l,r);
      else if(l>mid)return querySum((i<<1)|1,l,r);
        else return querySum(i<<1,l,mid)+querySum((i<<1)|1,mid+1,r);
}
int findMax(int u,int v)//查询u->v路径上节点的最大权值
{
    int f1=top[u],f2=top[v];
    int tmp=-1000000000;
    while(f1!=f2){
      if(deep[f1]<deep[f2]){
        swap(f1,f2);
        swap(u,v);
      }
      tmp=max(tmp,queryMax(1,p[f1],p[u]));
      u=fa[f1];
      f1=top[u];
    }
    if(deep[u]>deep[v]) swap(u,v);
    return max(tmp,queryMax(1,p[u],p[v]));
}
int findSum(int u,int v) //查询u->v路径上节点的权值的和
{
    int f1=top[u],f2=top[v];//记录所属链的根节点
    int tmp=0;
    while(f1!=f2){
      if(deep[f1]<deep[f2]){//同时向上跳
        swap(f1,f2);
        swap(u,v);
      }
      tmp+=querySum(1,p[f1],p[u]);
      u=fa[f1];
      f1=top[u];
    }
    if(deep[u]>deep[v]) swap(u,v);
    return tmp+querySum(1,p[u],p[v]);
}
int main()
{
//    freopen("in.in","r",stdin);
//    freopen("out.out","w",stdout);
    int n;
    int q;
    char op[20];
    int u,v;
    while(scanf("%d",&n)==1){
      init();
      for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
      }
      for(int i=1;i<=n;i++)
        scanf("%d",&s[i]);
      dfs1(1,0,0);
      getpos(1,1);
      build(1,0,pos-1);
      scanf("%d",&q);
      while(q--){
        scanf("%s%d%d",op,&u,&v);
        if(op[0]==‘C‘)  update(1,p[u],v);//修改单点的值
         else if(strcmp(op,"QMAX") == 0)
            printf("%d\n",findMax(u,v));//查询u->v路径上点权的最大值
         else printf("%d\n",findSum(u,v));//查询路径上点权的和
      }
    }
    return 0;
}

程序员不是打字员而是作家!

时间: 2024-08-29 21:34:30

浅谈树链剖分(C++、算法、树结构)的相关文章

浅谈树链剖分

今天刚学会树剖......(是不是觉得我很菜QwQ) 树剖的用处: 引子问题1: 给你一颗树,支持两种操作: 1.给x到y路径上的值加z 2.求出点x的值 简单,树上差分嘛,前几天刚学过啊. 引子问题2: 给你一颗树,支持两种操作: 1.给以x为根的子树加z 2.求出以x为根的子树的和. 简单,dfs序+线段树啊. 那么把两个问题结合起来呢?——树链剖分华丽丽登场!!! 树剖核心思想: 听说线段树挺好用的,区改区查只要log的复杂度,但是只能在线性结构上用,哎,真是太遗憾了. 听说有一种叫做df

蒟蒻浅谈树链剖分之一——两个dfs操作

树链剖分,顾名思义就是将树形的结构剖分成链,我们以此便于在链上操作 首先我们需要明白在树链剖分中的一些概念 重儿子:某节点所有儿子中子树最多的儿子 重链:有重儿子构成的链 dfs序:按重儿子优先遍历时的顺序 轻儿子的意思就与重儿子相反 首先是第一个dfs操作 在本次操作中,我们主要做的是处理所有节点的父亲,子树大小,重儿子,深度等操作 void dfs1(int now,int father,int deep) { tree[now].depth=deep;//初始化当前节点的深度,子树大小,父

树链剖分(轻重链剖分)算法笔记[转]

仔细想想 自己第一次听说这个这个数据结构大概有两年半的时间了 然而一直不会. 不过现在再回头来看 发现其实也不是很麻烦 首先 在学树链剖分之前最好先把LCALCA 树形DPDP 以及dfsdfs序 这三个知识点学了 如果这三个知识点没掌握好的话 树链剖分难以理解也是当然的 ------------------------------------------------------------------------------------------- 树链剖分通常用于处理树的形态不变 但点权/

树链剖分算法解析

本文部分内容参考自 这篇博客 (写的很好 Orz ,建议大家也去看一下) 树链剖分是什么?用来做什么? 有一棵树,求解以下问题:1将从 x 到 y 的路径上的每个结点权值增加 z2求从 x 到 y 的路径上的每个结点的权值和/权值最大值/权值最小值 单独求解每个问题都很简单,这两种问题结合起来,之前的方法就变得并不理想了. 而树链剖分可以巧妙地解决这一类问题. 树链剖分概述 树链剖分可以把一棵树分割成几条链, 原文地址:https://www.cnblogs.com/abc2237512422/

BZOJ 1036: [ZJOI2008]树的统计Count [树链剖分]

1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 14302  Solved: 5779[Submit][Status][Discuss] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I

【树链剖分】【分类讨论】水果姐逛水果街Ⅲ

3306 水果姐逛水果街Ⅲ 时间限制: 2 s 空间限制: 256000 KB 题目等级 : 大师 Master 题目描述 Description 连续两天都没有被cgh难倒,水果姐心情很不错,第三天又来逛水果街. 今天cgh早早就来到了水果街等水果姐,因为他带来了帮手cys.cgh的魔法是把水果街变成树结构,而cys的魔法是瞬间改变某家店的水果价格.一边问一边改,绝不给水果姐思考的时间! 同样还是n家水果店,编号为1~n,每家店能买水果也能卖水果,并且同一家店卖与买的价格一样. cgh和cys

【模板】树链剖分

树链剖分是一种应付树上修改和查询的算法(数据结构),要求树的形态不发生改变(改变的要用LCT维护) 树剖可以解决如下问题:路径修改(查询),子树修改(查询),单点修改. 其实有的题目DFS序即可,还有的要用点分治会明显方便一些. 本模板支持:输入p,q,查询p,q的路径上的权值和,给定p,w,将p子树权值增加w,单点增加权值(其实怎么搞都好,只要树的形态不改变,修改满足线段树要求) 1 #include<stdio.h> 2 #define maxn 1000 3 struct node{in

树链剖分求LCA

这里先推荐两道练习的裸题 首先是求点 [codevs4605] LCA 就是求两个点的公共祖先,每次询问xor上上一个询问的答案. 先是两遍DFS: dfs1:把dep.siz.son求出来 dfs2:求出top和w siz[v]表示以v为根的子树的节点数 dep[v]表示v的深度(根深度为1) top[v]表示v所在的链的顶端节点 fa[v]表示v的父亲 son[v]表示与v在同一重链上的v的儿子节点 w[v]结点编号 int lca(int x,int y){ while (top[x]!=

数据结构之树链剖分

首先了解一下基本概念: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子.      轻儿子:v的其它子节点.      重边:点v与其重儿子的连边.      轻边:点v与其轻儿子的连边.      重链:由重边连成的路径.      轻链:轻边. 剖分后的树有如下性质:      性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v]:      性质2:从根到某一点的路径上轻链.重链的个数都不大于logN. 树链剖分,如其字面意思,就是将一棵树按照轻重