午学树链剖分有感~

今天上课谈到树链剖分,被机房某dalao嘲讽了一波,决定冒着不听课的风险学链剖

关于这篇blog的灵感来源:传送门,不妥删

1.0前置知识

1.1链式向前星

这个应该都会吧,但还是附上讲解,这个图很易懂啊,蒟蒻当时一直没学懂,看了图才发现自己有多弱智

1.2dfs序

这个学长上课讲的随便找了一个讲解,仅供参考:传送门

1.3线段树

线段树应该都学了,就不附连接了

1.4为什么要学链剖

小蒟蒻没怎么做过树上倍增和链剖的题,但据说树上倍增的题链剖都能做,但链剖的题树上倍增不一定能做

2.0正文

2.1模板题传送门:洛谷p3384

2.2一些定义

  • 重儿子:父节点的所有儿子中子树结点数目最大的结点;
  • 轻儿子:父节点中除了重儿子以外的儿子;
  • 重边:父亲结点和重儿子连成的边;
  • 轻边:父亲节点和轻儿子连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

另:

  1. 如果一个点的一堆儿子所在子树大小相等且最大,那随便找一个做重儿子,这真的很严谨
  2. 叶节点没有重儿子,非叶节点有且仅有一个重儿子

图解(手绘不易,图丑勿喷,谢谢配合)

黄线:重边;黑线:轻边;

重链如1-4,2-11……轻链如2-5……

红色节点:重链所在起点(top[ ]);

重儿子重边连接的节点,其余为轻儿子;

每条边的值实为dfs顺序

2.3变量声明

struct node1{//邻接表,链式向前星
    int next,to;
    #define next(i) e[i].next
    #define to(i) e[i].to
}e[maxn*2];
struct node2{//线段树维护
    int l,r,ls,rs,sum,lazy;
    #define l(i) a[i].l
    #define r(i) a[i].r
    #define sum(i) a[i].sum
    #define lazy(i) a[i].lazy
    #define ls(i) a[i].ls
    #define rs(i) a[i].rs
}a[maxn*2];
int n,m,r,rt,mod,v[maxn],head[maxn],cnt,rk[maxn];//rk存当前dfs标号在树中所对应的节点
int fa[maxn],depth[maxn],son[maxn],size[maxn];//fa他爹,depth深度,son重儿子,size子树大小(包含自身)
int top[maxn],id[maxn];//top是当前节点所在链的顶端节点(记得之前的红节点吗),id树中每个节点剖分以后的新编号(DFS的执行顺序)

酷爱宏定义的我,首先要说一下这里所有的int 都代表long long

是不是有一丝迷茫?O(∩_∩)O没关系看看下面

2.4一些函数讲解

(1)add加边操作,详见链式向前星

inline void add(int x,int y){//链式向前星加边
    nex(++cnt)=head[x];to(cnt)=y;head[x]=cnt;
}

(2)dfs1,作用:处理fa,depth,size

inline void dfs1(int p){    size[p]=1,depth[p]=depth[fa[p]]+1;//更新size,depth    for(int i=head[x];i;i=next(i)){        int q=to(i);        if(q==fa[p])continue;//找到爸爸就返回        fa[q]=p,dfs1(q),size[p]+=size[q];        if(size[son[p]]<size[q])son[p]=q;//更新重儿子     }}

上图跑完以后是这个样子(这个直接截了dalao的图代表和上面一样,f代表fa,d代表depth)

(3)dfs2 作用:处理top,rk,id

inline void dfs2(int p,int tp){
    top[p]=tp,id[p]=++cnt,rk[cnt]=p;//更新数组
    if(son[p])dfs2(son[p],tp);//有重儿子就继续递归更新下去
    for(i=head[x];i;i=next(i)){
        int q=to(i);
        if(q!=fa[p]&&q!=son[p])
            dfs2(q,q);//显然位于轻链低端的点top为他本身
    }
}

如果对于id和rk仍然有些不懂,请看下图

其实rk和id就是反着的啦~

(4)pushup(p)操作用来更新以p为节点的子树的和

inline void pushup(int p){//左右子树更新和 sum(p)=sum(ls(p))+sum(rs(p)); sum(p)%=mod;}

(5)build(l,r,p)建树

inline void build(int l,int r,int p){//建树,p当前节点
    if(l==r){
        sum(p)=v[rk[l]];//或v[rk[r]]
        l(p)=r(p)=l;return ;//或=r
    }
    int mid=(l+r)>>1;
    ls(p)=cnt++,rs(p)=cnt++;
    build(l,mid,ls(p)),build(mid+1,r,rs(p));
    l(p)=l(ls(p)),r(p)=r(rs(p));
    pushup(p);//建了子树当然要更新一下和
}

(6)len(p)求长度

inline int len(int p){
    return r(p)-l(p)+1;
}

(7)pushdown(p)传以p为节点的子树的懒标记

inline void pushdown(int p){//传懒标记 ,不太懂得可以先巩固一遍线段树
    if(lazy(p)){
        int ls=ls(p),rs=rs(p),lz=lazy(p);
        (lazy(ls)+=lz)%=mod;(lazy(rs)+=lz)%=mod;
        (sum(ls)+=lz*len(ls))%=mod;
        (sum(rs)+=lz*len(rs))%=mod;
        lazy(p)=0;
    }
}

(8)update(l,r,d,p)修改某一子树

inline void update(int l,int r,int d,int p){//修改某一子树
    if(l(p)>=l&&r(p)<=r){
        lazy(p)+=d;lazy(p)%=mod;
        sum(p)+=len(p)*d;sum(p)%=mod;
        return ;
    }
    pushdown(p);
    int mid=(l(p)+r(p))>>1;
    if(mid>=l)update(l,r,d,ls(p));
    if(mid<r)update(l,r,d,rs(p));
    pushup(p);
}

(9)updates(x,y,d)从lca往下跳到x调用update再从lca跳到y调用update

inline void updates(int x,int y,int d){
    while(top[x]!=top[y]){
        if(depth[top[x]]<depth[top[y]])swap(x,y);//我们假设x更深
        update(id[top[x]],id[x],d,root);
        x=fa[top[x]];
    }
    if(id[x]>id[y])swap(x,y);
    update(id[x],id[y],d,root);
}

(10)query(l,r,p)询问某子树和

inline int query(int l,int r,int p){
    if(l(p)>=l&&r(p)<=r)return sum(p);
    pushdown(p);
    int mid=(l(p)+r(p))>>1,tot=0;
    if(mid>=l)tot+=query(l,r,ls(p));
    if(mid<r)tot+=query(l,r,rs(p));
    return tot%mod;
}

(11)ask(x,y)询问路径和(ask,query和updates,update道理是一样的)

inline int ask(int x,int y){//求路径和
    int ans=0;
    while(top[x]!=top[y]){
        if(depth[top[x]]<depth[top[y]])swap(x,y);
        ans+=query(id[top[x]],id[x],root);
        ans%=mod;
        x=fa[top[x]];
    }
    if(id[x]>id[y])swap(x,y);
    return ans+query(id[x],id[y],root)%mod;
}

(12)终于迎来了主函数!

signed main(){
    scanf("%lld%lld%lld%lld",&n,&m,&root,&mod);
    for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
    for(int i=1;i<=n-1;i++){
        int x,y;
        scanf("%lld%lld",&x,&y);
        add(x,y);add(y,x);
    }
    cnt=0;dfs1(root);dfs2(root,root);
    cnt=0;build(1,n,root=cnt++);
    while(m--){
        int type;
        scanf("%lld",&type);
        switch(type){
            case 1:{
                int x,y,d;
                scanf("%lld%lld%lld",&x,&y,&d);
                updates(x,y,d);
                break;
            }
            case 2:{
                int x,y;
                scanf("%lld%lld",&x,&y);
                printf("%lld\n",ask(x,y));
                break;
            }
            case 3:{
                int x,y;
                scanf("%lld%lld",&x,&y);
                update(id[x],id[x]+size[x]-1,y,root);
                break;
            }
            case 4:{
                int x;
                scanf("%lld",&x);
                printf("%lld\n",query(id[x],id[x]+size[x]-1,root));
                break;
            }
        }
    }
    return 0;
}

3.0后记

emmmm对于这道模板题……我死了,所以在这也就不发完整代码了,大家可以借鉴一下我在开头提到的dalao的blog

如果有dalao愿意把这些函数凑到一起帮我查个错,那就感激涕零了(?????)

国际惯例:撒花完结*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

原文地址:https://www.cnblogs.com/klmon666/p/10802766.html

时间: 2024-08-30 11:53:44

午学树链剖分有感~的相关文章

学树链剖分记

8102年,我接触到了一个奇怪鬼畜的数据结构: 前向星!!! 貌似与题目跑偏了 9102年,又接触到一个更加形容词省略的数据结构:线段树. 奇迹般繁琐 于是在第二天的模拟赛中挂掉了.(线段树模板只差3个字母,QWQ): 然后在第三天遇到了一个繁琐至极的数据结构:树链剖分!!! 终于进入正题了 下午听完$yyc$的课后,感觉貌似很简单的样子. 于是yyc就传下了一个奇怪的代码.(长度5208 QWQ) 于是就崩溃了. 突然在第一次到校日当天,我想做一下模板. 以下没有用的,读者可自行跳过. $x^

【树链剖分模板】【SPOJ 375】 Query on a tree

375. Query on a tree Problem code: QTREE You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3...N-1. We will ask you to perfrom some instructions of the following form: CHANGE i ti : change the cost of

SPOJ 375 树链剖分

点击打开链接 题意:给个树和树上的权值,两个操作,Q u v,问u到v的边上的最大权值,C u v,将第u条边的权值改为v 思路:今天学了学树链剖分,这题是个检验模版的题目,理论我是解释不清楚的,自己在九野聚聚那学来的一份模版 #include <vector> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <a

spoj Query on a tree(树链剖分模板题)

375. Query on a tree Problem code: QTREE You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3...N-1. We will ask you to perfrom some instructions of the following form: CHANGE i ti : change the cost of

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

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

树链剖分[模板](洛谷 P3384)

洛谷·[模板]树链剖分 写在前面 首先,在学树链剖分之前最好先把 LCA.树形DP.DFS序 这三个知识点学了 如果这三个知识点没掌握好的话,树链剖分难以理解也是当然的. 树链剖分 树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度 概念 dfs1() dfs2() 对剖过后的树建线段树 处理问题 概念 重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子 叶子节点没有重儿子

树链剖分详解

前言 树链剖分是什么? 树链剖分,说白了就是一种让你代码不得不强行增加1k的数据结构-dms 个人理解:+1:joy: 有什么用? 证明出题人非常毒瘤 可以非常友(bao)好(li)的解决一些树上问题:grimacing: (友情提示:学树链剖分之前请先掌握线段树) 核心思想 树链剖分的思想比较神奇 它的思想是:把一棵树拆成若干个不相交的链,然后用一些数据结构去维护这些链 那么问题来了 如何把树拆成链? 首先明确一些定义 重儿子:该节点的子树中,节点个数最多的子树的根节点(也就是和该节点相连的点

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

树链剖分小结

这两周在学树剖. 先扔个模板 有一类题目,要求实现一类在树上的操作,比如: 修改/求 树上某 节点/边权 的(最)值: 修改/求 树上某 节点/边权 及其子树上所有节点的(最)值: 修改/求 树上某两点路径间的 节点/边权 的(最)值: 乍一看似乎用线段树就可以实现,但是如果仔细想想,可以发现单凭线段树是无法解决的. 对于这类题目,常用的解决方法是树链剖分. 树链剖分(节选自starszys博客): 相关定义: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子. 轻儿子:v