ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树链抛分小记

2021-09-04 22:00:10  阅读:169  来源: 互联网

标签:sizx int top tr deep 树链 抛分 节点 小记


Powered by:NEFU AB_IN

文章目录

树链剖分

  • 介绍

    把“树”“剖分”成“链”

  • 前置知识

    • 线段树
    • 树的 d f s dfs dfs序
  • 主要思想

    我在这简单记录一下主要思想

    学习博客:树链剖分CSDN博客

    学习视频:树链剖分学习

    基本题型如下

    • 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
    • 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
    • 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
    • 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

    需要维护树上的路径,比如根据 d f s dfs dfs序的 4 − 8 4-8 4−8都加上一个数,为了可以简化成 l o g log log的操作,可以用加法线段树来维护,把这颗树拆成一条一条链

    • 重儿子:当前父节点中孩子数量最多的,也就是每一层只有一个重儿子
    • 轻儿子除重儿子外的其他子节点

    • 重边每个节点与其重儿子间的边

    • 轻边每个节点与其轻儿子间的边

    • 重链重边连成的链

    • 轻链轻边连成的链

      在这里插入图片描述

    比如上图,蓝色点就是轻儿子,红色点就是重儿子

    按重儿子递归就可以产生有序数列,比如上图,就可以按照重链 1 − 2 − 3 − 4 1-2-3-4 1−2−3−4,至于为什么 1 1 1是轻儿子,我觉得在 d f s 2 dfs2 dfs2中一开始设立 t o p top top比较方便,就把 1 1 1放在了轻儿子的地位上

    现在如果要求 4 − 8 4-8 4−8链上的和,那么无非就是两条链,一条 1 − 2 − 3 − 4 1-2-3-4 1−2−3−4,一条 1 − 8 1-8 1−8,核心思想就是比 4 4 4和 8 8 8谁的 t o p top top重,谁就优先跳,解释的话我简单来说,就是 t o p top top重的肯定在下面,那么往上跳就越和另一个 t o p top top聚拢,跟另一个 t o p top top一样了就更好操作,这个操作也可以用来求 l c a lca lca

    那么 8 8 8是轻儿子,他的 t o p top top就是他自己; 4 4 4是重儿子,他的 t o p top top是 1 1 1,那么可以看出 8 8 8的 t o p top top深,让 8 8 8跳,记下 8 − 8 8-8 8−8的和,然后 8 8 8变成他自己的父节点 1 1 1,这时候 4 4 4和 8 8 8的 t o p top top相同了,就可以直接用线段树求 1 − 4 1-4 1−4的和

  • 代码解析

    首先对于一个树,我们用链式前向星先建树

    const int N = 1e6 + 10;
    struct Edge
    {
        int v, ne;
    } e[N << 2];
    int h[N];
    int cnt;
    void add(int u, int v)
    {
        e[cnt].v = v;
        e[cnt].ne = h[u];
        h[u] = cnt++;
    }
    void init()
    {
        memset(h, -1, sizeof(h));
        cnt = 0;
    }
    

    其次,需要先对这个树进行一次 d f s dfs dfs,目的是为了标记重儿子,轻儿子,每个点的父节点以及每个点的深度

    int pre[N], sizx[N], son[N], deep[N];
    void dfs1(int u, int fa)
    {
        pre[u] = fa;
        deep[u] = deep[fa] + 1;
        sizx[u] = 1; // 初始u节点的子节点数量为1
        int maxson = -1;
        for (int i = h[u]; ~i; i = e[i].ne)
        {
            int v = e[i].v;
            if (v != fa) // 如果子节点不是父节点时递归
            {
                dfs1(v, u);
                sizx[u] += sizx[v]; // 父节点的子节点个数加上子节点统计的数量
                if (maxson < sizx[v])
                {
                    maxson = sizx[v];
                    son[u] = v; // 记录u的重儿子是v
                }
            }
        }
    }
    
    dfs1(1, 0) // 1的父节点是0
    

    处理好数据之后,第二遍 d f s dfs dfs根据重链标记 d f s dfs dfs序,并标记每个节点的 t o p top top

    int cnx; // dfs2 pool
    int dfn[N], top[N], a[N];
    void dfs2(int u, int t) // 因dfs1已经标记了重儿子,按重儿子递归,然后标记每个儿子的头
    {
        top[u] = t;
        dfn[u] = ++cnx;
        a[cnx] = w[u]; // 用a数组记录,下标为dfs序,值为次节点的初始值
        if (!son[u]) // 如果没有重儿子就返回
            return;
        dfs2(son[u], t); // 有就递归下去
        for (int i = h[u]; ~i; i = e[i].ne)
        {
            int v = e[i].v;
            if (v != pre[u] && v != son[u]) // 不能是父节点,也不能是重儿子,那么就是轻儿子
            {
                dfs2(v, v); //标记轻儿子,轻儿子的头就是它本身
            }
        }
    }
    
    dfs2(1, 1) // 1的top为1
    

    下面就是线段树的部分,因为接下来会有个例题,是线段树的区间加法+单点查询,所以就写这个了

    struct xds
    {
        int l, r, p, lazy;
    } tr[N << 2];
    
    void pushdown(int k)
    {
        if (tr[k].lazy)
        {
            tr[k << 1].p += tr[k].lazy;
            tr[k << 1 | 1].p += tr[k].lazy;
            tr[k << 1].lazy += tr[k].lazy;
            tr[k << 1 | 1].lazy += tr[k].lazy;
            tr[k].lazy = 0;
        }
    }
    
    void build(int k, int l, int r)
    {
        tr[k].l = l;
        tr[k].r = r;
        tr[k].lazy = 0;
        if (l == r)
        {
            tr[k].p = a[l];
            return;
        }
        int mid = l + r >> 1;
        build(k << 1, l, mid);
        build(k << 1 | 1, mid + 1, r);
    }
    
    void modify(int k, int ql, int qr, int w)
    {
        if (tr[k].l >= ql && tr[k].r <= qr)
        {
            tr[k].p += w;
            tr[k].lazy += w;
            return;
        }
        pushdown(k);
        int mid = tr[k].l + tr[k].r >> 1;
        if (ql <= mid)
            modify(k << 1, ql, qr, w);
        if (qr > mid)
            modify(k << 1 | 1, ql, qr, w);
    }
    
    int query(int k, int pos) //单点查询
    {
        if (tr[k].l == tr[k].r)
        {
            return tr[k].p;
        }
        pushdown(k);
        int mid = tr[k].l + tr[k].r >> 1;
        if (mid >= pos)
            query(k << 1, pos);
        else
            query(k << 1 | 1, pos);
    }
    
    
    build(1, 1, n)
    

    线段树写好了,那么就可以进行真正的树链剖分了

    void mtre(int x, int y, int z)
    {
        while (top[x] != top[y]) // 一直递归到top不同
        {
            if (deep[top[x]] < deep[top[y]]) // 挑选头重的(即深度大),优先进行操作
            {
                swap(x, y);
            }
            modify(1, dfn[top[x]], dfn[x], z); // 利用线段树,操作这一个链,(是对dfs序进行操作,而不是节点标号)
            x = pre[top[x]];                   // x变成x的父节点
        }
        if (deep[x] > deep[y])
        { // 头相同了,说明在同一条链上,这时挑个头轻的
            swap(x, y);
        }
        modify(1, dfn[x], dfn[y], z);
    }
    
  • 例题

  • HDU 3966 Aragorn’s Story

    • 题意

      给你一棵树,给你三种操作, u u u到 v v v 结点之间的结点加上 w w w,减去 w w w ,查询结点 u u u的权值?

    • 思路

      裸的树链剖分题,看操作就可以知道

    • 代码

      /*
       * @Author: NEFU AB_IN
       * @Date: 2021-09-04 18:43:00
       * @FilePath: \Vscode\ACM\Project\ShuLianPaoFen\hdu3966.cpp
       * @LastEditTime: 2021-09-04 20:10:16
       */
      #include <bits/stdc++.h>
      using namespace std;
      #define LL long long
      #define MP make_pair
      #define SZ(X) ((int)(X).size())
      #define IOS                      \
          ios::sync_with_stdio(false); \
          cin.tie(0);                  \
          cout.tie(0);
      #define DEBUG(X) cout << #X << ": " << X << endl;
      typedef pair<int, int> PII;
      
      const int N = 1e6 + 10;
      struct Edge
      {
          int v, ne;
      } e[N << 2];
      int h[N];
      int cnt;
      void add(int u, int v)
      {
          e[cnt].v = v;
          e[cnt].ne = h[u];
          h[u] = cnt++;
      }
      void init()
      {
          memset(h, -1, sizeof(h));
          memset(son, 0, sizeof son);
          cnx = 0;
          cnt = 0;
      }
      int w[N];
      int pre[N], sizx[N], son[N], deep[N];
      int dfn[N], top[N], a[N];
      int cnx; // dfs2 pool
      
      void dfs1(int u, int fa)
      {
          pre[u] = fa;
          deep[u] = deep[fa] + 1;
          sizx[u] = 1;
          int maxson = -1;
          for (int i = h[u]; ~i; i = e[i].ne)
          {
              int v = e[i].v;
              if (v != fa)
              {
                  dfs1(v, u);
                  sizx[u] += sizx[v];
                  if (maxson < sizx[v])
                  {
                      maxson = sizx[v];
                      son[u] = v;
                  }
              }
          }
      }
      
      void dfs2(int u, int t)
      {
          top[u] = t;
          dfn[u] = ++cnx;
          a[cnx] = w[u];
          if (!son[u])
              return;
          dfs2(son[u], t);
          for (int i = h[u]; ~i; i = e[i].ne)
          {
              int v = e[i].v;
              if (v != pre[u] && v != son[u])
              {
                  dfs2(v, v);
              }
          }
      }
      
      struct xds
      {
          int l, r, p, lazy;
      } tr[N << 2];
      
      void pushdown(int k)
      {
          if (tr[k].lazy)
          {
              tr[k << 1].p += tr[k].lazy;
              tr[k << 1 | 1].p += tr[k].lazy;
              tr[k << 1].lazy += tr[k].lazy;
              tr[k << 1 | 1].lazy += tr[k].lazy;
              tr[k].lazy = 0;
          }
      }
      
      void build(int k, int l, int r)
      {
          tr[k].l = l;
          tr[k].r = r;
          tr[k].lazy = 0;
          if (l == r)
          {
              tr[k].p = a[l];
              return;
          }
          int mid = l + r >> 1;
          build(k << 1, l, mid);
          build(k << 1 | 1, mid + 1, r);
      }
      
      void modify(int k, int ql, int qr, int w)
      {
          if (tr[k].l >= ql && tr[k].r <= qr)
          {
              tr[k].p += w;
              tr[k].lazy += w;
              return;
          }
          pushdown(k);
          int mid = tr[k].l + tr[k].r >> 1;
          if (ql <= mid)
              modify(k << 1, ql, qr, w);
          if (qr > mid)
              modify(k << 1 | 1, ql, qr, w);
      }
      
      int query(int k, int pos) //单点查询
      {
          if (tr[k].l == tr[k].r)
          {
              return tr[k].p;
          }
          pushdown(k);
          int mid = tr[k].l + tr[k].r >> 1;
          if (mid >= pos)
              query(k << 1, pos);
          else
              query(k << 1 | 1, pos);
      }
      
      void mtre(int x, int y, int z)
      {
          while (top[x] != top[y])
          {
              if (deep[top[x]] < deep[top[y]]) 
              {
                  swap(x, y);
              }
              modify(1, dfn[top[x]], dfn[x], z); 
              x = pre[top[x]];                   
          }
          if (deep[x] > deep[y])
          { 
              swap(x, y);
          }
          modify(1, dfn[x], dfn[y], z);
      }
      
      signed main()
      {
          IOS int n, m, q;
          while (cin >> n >> m >> q)
          {
              init();
              for (int i = 1; i <= n; ++i)
              {
                  cin >> w[i];
              }
              for (int i = 1; i <= m; ++i)
              {
                  int u, v;
                  cin >> u >> v;
                  add(u, v);
                  add(v, u);
              }
              dfs1(1, 0);
              dfs2(1, 1);
              build(1, 1, n);
              while(q --){
                  char c;
                  cin >> c;
                  if(c == 'I'){
                      int u, v, w;
                      cin >> u >> v >> w;
                      mtre(u, v, w);
                  }
                  if(c == 'D'){
                      int u, v, w;
                      cin >> u >> v >> w;
                      mtre(u, v, -w);
                  }
                  if(c == 'Q'){
                      int u;
                      cin >> u;
                      cout << query(1, dfn[u]) << '\n';
                  }
              }
          }
          return 0;
      }
      

重写了一遍线段树,感觉这版的线段树比较好写,就是别忘了查询和更新时都需要进行 p u s h d o w n pushdown pushdown的操作

还有边写边用快捷键进行格式化文档,可以看上去更美观一些。。。

完结。

标签:sizx,int,top,tr,deep,树链,抛分,节点,小记
来源: https://blog.csdn.net/qq_45859188/article/details/120106342

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有