zoukankan      html  css  js  c++  java
  • [note]树链剖分

    树链剖分https://www.luogu.org/problemnew/show/P3384

    概念

    树链剖分,是一种将树剖分成多条不相交的链的算法,并通过其他的数据结构来维护这些链上的信息。
    最简单的例子就是(LCA),假设现在有一棵退化成链的树。如果要求任意两点的(LCA),因为他们在同一条链上的缘故,只需要判断一下两者的深度就行了。由此可见,在链上是比在树上更好操作的。
    那么该怎么将一棵树剖分开来捏?
    先搬出一堆概念:

    重儿子
    在以X节点为根的子树中,节点数最多的子树的根节点,即是X节点的重儿子。
    重边
    连接X节点与X节点的重儿子的边,我们叫他重边。
    重链
    一堆重边连起来的链。
    轻链
    一堆非重边连起来的链。
    

    对于每个节点,找出其重儿子,就可以剖分成一条条重链与轻链。

    实现

    数组定义

    (val[N])每个节点的初值
    (size[N])每个节点子树的大小
    (son[N])每个节点的重儿子
    (fa[N])每个节点的父亲
    (dfn[N])每个节点在线段树上的编号
    (rk[N])线段树上节点在树中的编号
    (dep[N])节点深度
    (top[N])每个点所在链的链顶

    (dfs)

    第一遍(dfs)处理出(size[],fa[],son[],dep[])
    第二遍(dfs)处理出(top[],dfn[],rk[])

    线段树

    用线段树维护树链,并实现链上的操作,常见操作如下:
    将树从(X)(Y)结点最短路径上所有节点的值都加上(Z)
    求树从(X)(Y)结点最短路径上所有节点的值之和
    将以(X)为根节点的子树内所有节点值都加上(Z)
    求以(X)为根节点的子树内所有节点值之和
    对于子树操作:我们知道一颗子树内的编号一定是连续的,那么以(X)节点为根的子树的区间就是((dfn[x],dfn[x]+size[x]-1))

    #define RG register
    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int N=1e5+5;
    inline int read()
    {
        RG int x=0,w=1;RG char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*w;
    }
    int n,m,r,p,cnt,ct;
    int lazy[N<<2],sum[N<<2];
    int val[N],size[N],son[N],fa[N],dfn[N],rk[N],last[N],dep[N],top[N];
    struct edge{
        int to,next,w;
    }e[N<<1];
    void insert(int u,int v)
    {
        e[++cnt]=(edge){v,last[u]};last[u]=cnt;
        e[++cnt]=(edge){u,last[v]};last[v]=cnt;
    }
    inline void dfs1(int now)
    {
        size[now]=1;
        for(int i=last[now];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==fa[now])continue;
    		fa[v]=now;
    		dep[v]=dep[now]+1;
    		dfs1(v);
    		size[now]+=size[v];
    		if(size[v]>size[son[now]])son[now]=v;
        }
    }
    inline void dfs2(int now,int Top)
    {
        top[now]=Top;dfn[now]=++ct;rk[ct]=now;
        if(son[now])dfs2(son[now],Top);//重儿子
        for(int i=last[now];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==son[now]||v==fa[now])continue;
            dfs2(v,v);//轻儿子的top就是本身
        }
    }
    inline void Pushup(int root){sum[root]=(sum[root<<1]+sum[root<<1|1])%p;}
    void Build(int root,int l,int r)//建树
    {
        if(l==r){sum[root]=val[rk[l]];return;}
    	int mid=(l+r)>>1;
        Build(root<<1,l,mid);
        Build(root<<1|1,mid+1,r);
        Pushup(root);
    }
    inline void Pushdown(int root,int len)//下放懒标记,len为区间长度
    {
        if(!lazy[root])return;
    	lazy[root<<1]=(lazy[root<<1]+lazy[root])%p;
    	lazy[root<<1|1]=(lazy[root<<1|1]+lazy[root])%p;
    	sum[root<<1]=(sum[root<<1]+lazy[root]*(len-(len>>1))%p)%p;
    	sum[root<<1|1]=(sum[root<<1|1]+lazy[root]*(len>>1)%p)%p;
    	lazy[root]=0;
    }
    void Modify(int root,int l,int r,int ll,int rr,int k)//区间修改
    {
        Pushdown(root,r-l+1);
        if(ll<=l&&r<=rr)
        {
            lazy[root]=(lazy[root]+k)%p;
            sum[root]=(sum[root]+k*(r-l+1)%p)%p;
            return;
        }
    	int mid=(l+r)>>1;
        if(ll<=mid)Modify(root<<1,l,mid,ll,rr,k);
        if(rr>mid)Modify(root<<1|1,mid+1,r,ll,rr,k);
        Pushup(root);
    }
    int Query(int root,int l,int r,int ll,int rr)//区间查询
    {
        Pushdown(root,r-l+1);
        if(ll<=l&&r<=rr)return sum[root];
    	int mid=(l+r)>>1,Sum=0;
        if(ll<=mid)Sum=(Sum+Query(root<<1,l,mid,ll,rr))%p;
        if(mid<rr)Sum=(Sum+Query(root<<1|1,mid+1,r,ll,rr))%p;
        Pushup(root);
        return Sum;
    }
    inline void Modify_Tree(int x,int y,int k)//修改节点x到节点y路径上点的点权
    {
        while(top[x]!=top[y])//不在同一条链上
        {
            if(dep[top[x]]<dep[top[y]])swap(x,y);//注意是比较链顶的深度
            Modify(1,1,n,dfn[top[x]],dfn[x],k);//链顶更深的节点跳链
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])swap(x,y);
        Modify(1,1,n,dfn[x],dfn[y],k);//同一条链上,直接区间查询
    }
    inline int Query_Tree(int x,int y)//查询节点x到节点y路径上点权和
    {
        int Sum=0;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])swap(x,y);
            Sum=(Sum+Query(1,1,n,dfn[top[x]],dfn[x]))%p;
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])swap(x,y);
        return (Sum+Query(1,1,n,dfn[x],dfn[y]))%p;
    }
    inline void Modify_Son(int x,int k)//子树修改
    {
        Modify(1,1,n,dfn[x],dfn[x]+size[x]-1,k);
    }
    inline int Query_Son(int x)
    {
        return Query(1,1,n,dfn[x],dfn[x]+size[x]-1);//子树查询
    }
    int main()
    {
        n=read(),m=read(),r=read(),p=read();
        for(int i=1;i<=n;i++)val[i]=read()%p;
        for(int i=1;i<n;i++)insert(read(),read());
        dep[r]=1;
        dfs1(r);
        dfs2(r,r);
        Build(1,1,n);
        while(m--)
        {
            int f=read();
            if(f==1){int x=read(),y=read(),z=read()%p;Modify_Tree(x,y,z);}
            if(f==2)printf("%d
    ",Query_Tree(read(),read()));
            if(f==3){int x=read(),y=read()%p;Modify_Son(x,y);}
            if(f==4)printf("%d
    ",Query_Son(read()));
        }
        return 0;
    }
    
  • 相关阅读:
    sql server 2005的分页函数ROW_NUMBER
    Silverlight 皮肤(主题)动态切换
    Silverlight 地图导航
    WPF模板概述(数据模板)
    Silverlight MVVM Prism WCF RIA项目框架
    多线程通中的AutoResetEvent与ManualResetEvent
    Silverlight 图形报表 线形图 柱形图 饼图
    WPF 重要新概念读书笔记(转)
    Net基本概念和原理
    数据库辅助工具SqlDbx
  • 原文地址:https://www.cnblogs.com/sdzwyq/p/8432053.html
Copyright © 2011-2022 走看看