zoukankan      html  css  js  c++  java
  • P3384 【模板】树链剖分

    树链剖分 时间!!!!

    首先要学会线段树。由于线段树是基本技能,所以不再此过多解释。

    树链剖分操作如下

    操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z

    操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

    操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

    操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

    还有几个概念:重儿子,轻儿子,重链,轻链。

    众所周知,以每一个节点为根节点下所成的树(语文技能崩溃)都有一个大小(节点的数量)

    如果有一个儿子体型(节点数)在所有兄弟姐妹中最大,那么这个儿子就是这个节点的重儿子;

    其他的儿子都是轻儿子。

    重边就是每两个重儿子之间的边(手拉手,我们都是重儿子);轻边就是剩下的;

    重链就是相邻的每个重边连起来的链,包括一条端点重儿子和轻儿子之间的边,也就是所有重链以一个轻儿子开始;

    看完定义该干事了!!!

    首先,处理dep[],fa[],siz[],son[]

    void dfs1(int u,int fa)
    {
        f[u]=fa;//记录u节点的父亲 
        siz[u]=1;//子树大小,包括他自己 
        dep[u]=dep[fa]+1;//深度 
        for(int p=last[u];p;p=pre[p])
        {
            int v=other[p];
            if(v==fa) continue;
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[v]>siz[son[u]]) son[u]=v;//记录重儿子 
        
        }
    }

    second 新编,赋值,找头,先重再轻

    因为先处理重儿子就会让重儿子之间的编号是连续的。

    void dfs2(int u,int tp)
    {
        id[u]=++cnt;//重新编号 
        top[u]=tp;//这个点所在链的顶端 
        b[cnt]=a[u];//新编号赋值
        if(!son[u]) return ;//no son
        dfs2(son[u],tp);
        for(int p=last[u];p;p=pre[p])
        {
            int v=other[p];
            if(v==son[u]||v==f[u]) continue;
            dfs2(v,v);//对于每一个轻儿子都有一条从它自己开始的链 
        }
        
    }

    third 套用线段树。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=100050;
    int n,m,s,mo,a[maxn];
    int pre[maxn*2],other[maxn*2],last[maxn],l;
    int f[maxn],siz[maxn],dep[maxn],son[maxn];
    int id[maxn],cnt,top[maxn],b[maxn];
    
    void add(int x,int y)
    {
        l++;
        pre[l]=last[x];
        last[x]=l;
        other[l]=y;
    }
    //下面是线段树 
    struct tree
    {
        int l,r;
        int sum,lazy;
    }t[4*maxn];//四倍,前人的经验 
    
    void build(int x,int l,int r)//建树 
    {
        t[x].l=l;t[x].r=r;
        if(l==r)
        {
            t[x].sum=b[l];
            return ;
        }
        int mid=(l+r)>>1;
        build(2*x,l,mid);
        build(2*x+1,mid+1,r);
        t[x].sum=(t[2*x].sum+t[2*x+1].sum)%mo;
    }
    
    void update(int x)//更新懒标签 
    {
        t[2*x].sum+=((t[2*x].r-t[2*x].l+1)*t[x].lazy)%mo;
        t[2*x+1].sum+=((t[2*x+1].r-t[2*x+1].l+1)*t[x].lazy)%mo;
        t[2*x].lazy+=t[x].lazy;t[2*x].lazy%=mo;
        t[2*x+1].lazy+=t[x].lazy;t[2*x+1].lazy%=mo;
        t[x].lazy=0;
    }
    void change(int x,int l,int r,int z)//改变值 
    {
        if(t[x].l==l&&t[x].r==r)
        {
            t[x].sum+=(t[x].r-t[x].l+1)*z;
            t[x].lazy+=z;
            return ;
        }
        if(t[x].lazy) update(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(r<=mid) change(2*x,l,r,z);
        else if(l>mid) change(2*x+1,l,r,z);
        else
        {
            change(2*x,l,mid,z);
            change(2*x+1,mid+1,r,z);
        }
        t[x].sum=(t[2*x].sum+t[2*x+1].sum)%mo;
    }
    
    int query(int x,int l,int r)//查询区间和 
    {
        if(t[x].l==l&&t[x].r==r)
        {
            return t[x].sum;
        }
        if(t[x].lazy) update(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(r<=mid) return query(2*x,l,r);
        else if(l>mid) return query(2*x+1,l,r);
        else 
        {
            return query(2*x,l,mid)+query(2*x+1,mid+1,r);
        }
    }
    //上面是线段树 
    void dfs1(int u,int fa)
    {
        f[u]=fa;//记录u节点的父亲 
        siz[u]=1;//子树大小,包括他自己 
        dep[u]=dep[fa]+1;//深度 
        for(int p=last[u];p;p=pre[p])
        {
            int v=other[p];
            if(v==fa) continue;
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[v]>siz[son[u]]) son[u]=v;//记录重儿子 
        
        }
    }
    
    void dfs2(int u,int tp)
    {
        id[u]=++cnt;//重新编号 
        top[u]=tp;//这个点所在链的顶端 
        b[cnt]=a[u];//新编号赋值
        if(!son[u]) return ;//no son
        dfs2(son[u],tp);
        for(int p=last[u];p;p=pre[p])
        {
            int v=other[p];
            if(v==son[u]||v==f[u]) continue;
            dfs2(v,v);//对于每一个轻儿子都有一条从它自己开始的链 
        }
        
    }
    
    void tchange(int x,int y,int z)
    {
        z%=mo;
        while(top[x]!=top[y])//如果不在一条链上 
        {
            if(dep[top[x]]<dep[top[y]]) swap(x,y);//从深度小的来 
            change(1,id[top[x]],id[x],z);//一条链上加上z 
            x=f[top[x]];
        }//到一条链上之后跳出 
        if(dep[x]>dep[y]) swap(x,y);//找到深度浅的那个 
        change(1,id[x],id[y],z);//最后一条链加z 
    }
    
    int tcha(int x,int y)
    {
        int ans=0;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans+=query(1,id[top[x]],id[x]);
            ans%=mo;
            x=f[top[x]];
        }
        if(dep[x]>dep[y]) swap(x,y);
        ans+=query(1,id[x],id[y]);
        ans%=mo;
        return ans;
    }
    
    void changeson(int x,int z)
    {
        change(1,id[x],id[x]+siz[x]-1,z);//新编节点中每个节点和儿子们是连续的多么重要 
    }
    
    int chason(int x)
    {
        return query(1,id[x],id[x]+siz[x]-1);
    }
    int main()
    {
        scanf("%d%d%d%d",&n,&m,&s,&mo);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=1;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            add(b,a);
        }
        dfs1(s,0);
        dfs2(s,s);
        build(1,1,n);
        for(int i=1;i<=m;i++)
        {
            int op,x,y,z;
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d%d%d",&x,&y,&z);
                tchange(x,y,z);// 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
            }
            else if(op==2)
            {
                scanf("%d%d",&x,&y);
                printf("%d
    ",tcha(x,y)%mo);// 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
            }
            else if(op==3)
            {
                scanf("%d%d",&x,&z);// 3 x z 表示将以x为根节点的子树内所有节点值都加上z
                changeson(x,z);
            }
            else if(op==4)
            {
                scanf("%d",&x);
                printf("%d
    ",chason(x)%mo);// 4 x 表示求以x为根节点的子树内所有节点值之和
            }
        }
        
        
        return 0;
    }

    刚学会,理解还是不太到位,欢迎指正。

    温馨提示(放在最后坑你们):每个加减计算都要取模。。。。。。

  • 相关阅读:
    201521123044 《Java程序设计》第2周作业-Java基本语法与类库
    201521123044 《Java程序设计》第01周学习总结
    201521123044 《Java程序设计》第1周学习总结
    Java 第七周总结
    Java 第六周总结
    Java 第五周总结
    test

    Java 第四周总结
    Java 第三周总结
  • 原文地址:https://www.cnblogs.com/WHFF521/p/11070147.html
Copyright © 2011-2022 走看看