zoukankan      html  css  js  c++  java
  • 树链剖分(1)

    ---恢复内容开始---

    对于一个树的图,有如下概念

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

    基本原理,就是对于一组树形的数据结构的操作区间取值,采用将树分成链,然后利用数据结构(线段树、树状数组等)来维护这些链。

    siz[u] 保存以u为根的子树节点个数     dfs1   
    top[u] 保存当前节点所在链的顶端节点  dfs2
    son[u] 保存重儿子            dfs1
    dep[u] 保存结点u的深度值        dfs1
    faz[u] 保存结点u的父亲节点        dfs1
    tid[u] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)  dfs2
    rnk[u] 保存当前节点在树中的位置              dfs2

    还有两条性质:

    1. 如果(u, v)是一条轻边,那么size(v)  <  size(u)  /  2; ps:因为重点是子节点数最多的了
    2. 从根结点到任意结点的路所经过的轻重链的个数必定都小与O(logn);

    再次想一想我们需要什么

    ***节点的重儿子son数组

    ***该节点的父亲f数组

    ***节点的根节点top数组

    ***该节点的子节点个数siz数组

    ***该节点的深度dep数组

    ***dfs中该节点被访问的顺序did数组

    ***dfs中访问顺序所对应的节点

    两次dfs

    维护线段树

    进行LCA查询操作

     如果改查一个节点和其子树————dfs序查询一个节点和其子树————dfs序 + 线段树维护
     如果改查一条链或者不同链上的任意两个点,线段树就不够了需要用到LCA并且利用top数组实现优化,快速计算

     初始准备工作:

    //https://www.luogu.org/problemnew/show/P3384
    //https://www.cnblogs.com/George1994/p/7821357.html
    #include <iostream>
    #include <cstdio>
    #include <string.h>
    #include <cmath>
    #include <algorithm>
    #define inf (1 << 28)
    #define lson rt<<1,left,mid
    #define rson rt<<1|1,mid+1,right
    #define ls rt<<1
    #define rs rt<<1|1
    using namespace std;
    typedef long long ll;
    int n,m,root,Mod;//节点个数,操作个数,根节点序号,取模数
    const int maxn = 1e5 + 1e3;
    int V[maxn];
    //step1 构建线段树
    ll lazy[maxn<<2];//经典之lazy标记
    ll val[maxn<<2];//树中的节点对应的值(树中的节点就是dfs的序号)
    /*dfs1*/
    int siz[maxn];
    int dep[maxn];
    int son[maxn];
    int fa[maxn];
    /*dfs2*/
    int did[maxn];//dfs序对应的原数据的节点值 --- 通过节点得知dfs序号
    int rnk[maxn];//这个节点的值对应的dfs序 --- 通过序号得知节点号
    int top[maxn];
    int tot;
    struct node{
        int to,pre;
    }e[maxn << 1];
    int id[maxn],cnt;
    

    两次dfs得到我们所需要的东西

    void dfs1(int rt,int f,int depth)
    {
        fa[rt] = f;
        dep[rt] = depth;
        siz[rt] = 1;
    
        for(int i = id[rt];~i;i = e[i].pre)
        {
            int to = e[i].to;
            if(to != f)
            {
                dfs1(to,rt,depth+1);
                siz[rt] += siz[to];
                if(siz[to] > siz[son[rt]])
                    son[rt] = to;
            }
        }
    
    }
    void dfs2(int now,int rt)
    {
        top[now] = rt;
        did[now] = ++tot;
        rnk[tot] = now;
    
        if(son[now])
            dfs2(son[now],rt);
        else
            return;
    
        for(int i = id[now];~i;i = e[i].pre)
        {
            int to = e[i].to;
            if(to != fa[now] && to != son[now])
            {
                dfs2(to,to);
            }
        }
    }
    

     然后对一些必要数据的初始化

    void init()
    {
        memset(id,-1,sizeof(id));
        memset(siz,0,sizeof(siz));
        memset(son,0,sizeof(son));
        cnt = 0;
        tot = 0;
    }
    void add(int from,int to)
    {
        e[cnt].to = to;
        e[cnt].pre = id[from];
        id[from] = cnt++;
    }
    

     然后针对线段树的常规操作

    void pup(int rt)
    {
        val[rt] = val[ls] + val[rs];
    }
    void build(int rt,int left,int right)
    {
        lazy[rt] = 0;
        if(left == right)
            val[rt] = V[rnk[left]];
        else
        {
            int mid = (left + right) >> 1;
            build(lson);
            build(rson);
            pup(rt);
        }
    
        /*
        PS:rt就是rt,我要访问也是通过left和right来访问操作,所以不必如此
        */
    }
    void pdown(int rt,int left,int right)
    {
        if(lazy[rt])
        {
            int lt = lazy[rt];
            int mid = (left + right) >> 1;
            val[ls] += (mid - left + 1) * lt;
            val[rs] += (right - mid) * lt;
    
            lazy[rs] += lt;
            lazy[ls] += lt;
            lazy[rt] = 0;
        }
    }
    void update(int rt,int left,int right,int l,int r,ll k)
    {
        if(l <= left && right <= r)
        {
            val[rt] += (right - left + 1) * k;
            lazy[rt] += k;
            return;
        }
        if(left > r || right < l)return;
    
        pdown(rt,left,right);
    
        int mid = (left + right) >> 1;
        if(left <= mid)
            update(lson,l,r,k);
        if(right > mid)
            update(rson,l,r,k);
        pup(rt);
    }
    ll query(int rt,int left,int right,int l,int r)
    {
        ll res = 0;
    
        if(l <= left && right <= r)
        {
            return val[rt];
        }
        if(left > r || right < l)return 0 ;
        pdown(rt,left,right);
    
        int mid = (left + right) >> 1;
    
        if(left <= mid)
            res += query(lson,l,r);
        res %= Mod;
        if(right > mid)
            res += query(rson,l,r);
        return res % Mod;
    }
    

     最主要的就是LCA查询操作

    线段树可以提供查询连续的区间值,但如果区间不连续,我们可以利用LCA将深度低的点利用top数组往上跳,记录中间的权值

    一旦在一条链上则直接利用线段树,改查都是这样的思想

    ll query_lca(int x,int y)
    {
        ll res = 0;
        while(top[x] != top[y])
        {
            if(dep[top[x]] < dep[top[y]])swap(x,y);
            res += query(1,1,tot,did[top[x]],did[x]);
            x = fa[top[x]];
            res %= Mod;
        }
        if(dep[x] < dep[y])swap(x,y);
    
        res += query(1,1,tot,did[y],did[x]);
    
        return res % Mod;
    }
    void updata_lca(int x,int y,int z)
    {
        while(top[x] != top[y])
        {
            if(dep[top[x]] < dep[top[y]])swap(x,y);
            update(1,1,tot,did[top[x]],did[x],z);
            x = fa[top[x]];
        }
        if(dep[x] < dep[y])swap(x,y);
        update(1,1,tot,did[y],did[x],z);
    }
    

     最后就简单略

    int main()
    {
        while(~scanf("%d%d%d%d",&n,&m,&root,&Mod))
        {
            init();
            for(int i = 1;i <= n;i++ )
            {
                scanf("%d",&V[i]);
            }
            int from, to;
            for(int i = 0;i < n-1;i++)
            {
                scanf("%d%d",&from,&to);
                add(from,to);
                add(to,from);
            }
            dfs1(root,root,1);
            dfs2(root,root);
            //cout<<tot<<endl;对
            build(1,1,tot);
            int op,x,y,z;
            for(int i = 0;i < m;i++)
            {
                scanf("%d",&op);
                if(op == 1)
                {
                    scanf("%d%d%d",&x,&y,&z);
                    updata_lca(x,y,z);
                }
                else if(op == 2)
                {
                    scanf("%d%d",&x,&y);
                    printf("%lld
    ",query_lca(x,y));
    
                }
                else if(op == 3)
                {
                    scanf("%d%d",&x,&z);
                    update(1,1,tot,did[x],did[x]+siz[x] - 1,z);
                }
                else if(op == 4)
                {
                    scanf("%d",&x);
                    printf("%lld
    ",query(1,1,tot,did[x],did[x] + siz[x] - 1));
                }
            }
        }
        return 0;
    }
    

     PS所要注意一点的是 longlong的情况,都要考虑到略,别有的地方longlong了有的地方没有longlong

  • 相关阅读:
    dsu on tree
    bzoj3527 [Zjoi2014]力
    bzoj3527 [Zjoi2014]力
    114.遍历文件夹并批量修改文件名
    25.八皇后问题
    24.C语言最全排序方法小结(不断更新)
    112.备忘录设计模式
    110.文件搜索,系统大小获取,以及病毒行为
    109.vprintf vfprintf vscanf vfscanf
    108.sqllite3(C语言数据库库)详解
  • 原文地址:https://www.cnblogs.com/DF-yimeng/p/9575260.html
Copyright © 2011-2022 走看看