树链剖分 学习笔记

裂缝中的阳光-林俊杰
头图

有多少创伤卡在咽喉 有多少眼泪滴湿枕头
有多少你觉得不能够 被懂的痛 只能沉默
有多少夜晚没有尽头 有多少的寂寞 无人诉说
有多少次的梦 还没做 已成空
等到黑夜翻面之后 会是新的白昼
等到海啸退去之后 只是潮起潮落
别到最后你才发觉 心里头的野兽
还没到最终就已经罢休
心脏没有那么脆弱 总还会有执着
人生不会只有收获 总难免有伤口
不要害怕生命中 不完美的角落
阳光在每个裂缝中散落
就算一切重来又怎样 让你的心在我 心上跳动
每个逐渐暗下来的夜 一起走过
等到黑夜翻面之后 会是新的白昼
等到海啸退去之后 只是潮起潮落
别到最后你才发觉 心里头的野兽
还没到最终就已经罢休
心脏没有那么脆弱 总还会有执着
人生不会只有收获 总难免有伤口
不要害怕生命中 不完美的角落
阳光在每个裂缝中散落
不如就勇敢打破 生命中的裂缝
阳光就逐渐洒满了其中


树链剖分 学习笔记

时更。

板子
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
typedef long long ll;
inline int qr()
{
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#define qr qr()
using namespace std;
const int Ratio=0;
const int N=500005;
const int maxi=INT_MAX;
int n,m,tot,cnt;
int hh[N],to[N],ne[N];
int dep[N],fa[N],siz[N],son[N];
int id[N],top[N];
// int w[N],wt[N];
namespace Wisadel
{
	void Wadd(int u,int v/*,int ww*/)
    {
        to[++cnt]=v;
        // w[cnt]=ww;
        ne[cnt]=hh[u];
        hh[u]=cnt;
    }
    void Wdfs1(int u,int f,int deep)
    {
        dep[u]=deep,fa[u]=f,siz[u]=1;
        int maxson=-1;
        for(int i=hh[u];i!=-1;i=ne[N])
        {
            int v=to[i];
            if(v==f)
                continue;
            Wdfs1(v,u,deep+1);
            siz[u]+=siz[v];
            if(siz[v]>maxson)
                son[u]=v,maxson=siz[v];
        }
    }
    void Wdfs2(int u,int topf)
    {
        id[u]=++tot,top[u]=topf;
        // wt[tot]=w[u];
        if(!son[u])
            return;
        Wdfs2(son[u],topf);
        for(int i=hh[u];i!=-1;i=ne[i])
        {
            int v=to[i];
            if(v==fa[u]||v==son[u])
                continue;
            Wdfs2(v,v);
        }
    }
    short main()
	{
		memset(hh,-1,sizeof hh);
		return Ratio;
	}
}
int main(){return Wisadel::main();}

概念

树链剖分,就是对一棵树分成几条链,把树形变为线性,减少处理难度。

需要处理的问题:

  • 将树从 \(x\)\(y\) 结点最短路径上所有节点的值都加上 \(z\)
  • 求树从 \(x\)\(y\) 结点最短路径上所有节点的值之和;
  • 将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)
  • 求以 \(x\) 为根节点的子树内所有节点值之和。

image

——转载自博客树链剖分详解(洛谷模板 P3384)

食用方法

先进行 LCA ,用两个 dfs 完成一些初始化。

第一个,负责处理:

  1. 每个点的深度 \(dep[]\)
  2. 每个点的父亲 \(fa[]\)
  3. 每个非叶子节点的子树大小 \(siz[]\)
  4. 每个非叶子节点的重儿子编号 \(son[]\)

第二个,负责处理:

  1. 每个点的新编号 \(id[]\)
  2. 为新点赋值 \(wt[]\)
  3. 每个点所在链的顶端 \(top[]\)
  4. 处理每条链。

再进行线段树的相关操作

线段树就不用赘述了吧。

例题 P3384 【模板】重链剖分/树链剖分

题链

主要题干:
image

用到区间修改,区间求值。

给了根节点编号,直接用即可。

code:
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
typedef long long ll;
inline int qr()
{
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#define qr qr()
using namespace std;
const int Ratio=0;
const int N=500005;
const int maxi=INT_MAX;
int n,m,tot,cnt,r,mod;
int hh[N],to[N],ne[N];
int dep[N],fa[N],siz[N],son[N];
int id[N],top[N];
int a[N<<2],laz[N<<2];
int res;
int w[N],wt[N];
namespace Wisadel
{
	void Wadd(int u,int v/*,int ww*/)
    {
        to[++cnt]=v;
        // w[cnt]=ww;
        ne[cnt]=hh[u];
        hh[u]=cnt;
    }
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    #define mid ((l+r)>>1)
    void Wpushdown(int rt,int len)
    {
        laz[ls]+=laz[rt],laz[rs]+=laz[rt];
        a[ls]+=laz[rt]*(len-(len>>1));
        a[rs]+=laz[rt]*(len>>1);
        a[ls]%=mod,a[rs]%=mod;
        laz[rt]=0;
    }
    void Wbuild(int rt,int l,int r)
    {
        if(l==r)
        {
            a[rt]=wt[l];
            if(a[rt]>mod)
                a[rt]%=mod;
            return;
        }
        Wbuild(ls,l,mid);
        Wbuild(rs,mid+1,r);
        a[rt]=(a[ls]+a[rs])%mod;
    }
    void Wque(int rt,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
        {
            res=(res+a[rt])%mod;
            return;
        }
        if(laz[rt])
            Wpushdown(rt,r-l+1);
        if(x<=mid)
            Wque(ls,l,mid,x,y);
        if(y>mid)
            Wque(rs,mid+1,r,x,y);
    }
    void Wupd(int rt,int l,int r,int x,int y,int k)
    {
        if(x<=l&&r<=y)
        {
            laz[rt]+=k;
            a[rt]+=k*(r-l+1);
            return;
        }
        if(laz[rt])
            Wpushdown(rt,r-l+1);
        if(x<=mid)
            Wupd(ls,l,mid,x,y,k);
        if(y>mid)
            Wupd(rs,mid+1,r,x,y,k);
        a[rt]=(a[ls]+a[rs])%mod;
    }
    #undef ls
    #undef rs
    #undef mid
    int Wqr(int x,int y)
    {
        int ans=0;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            res=0;
            Wque(1,1,n,id[top[x]],id[x]);
            ans=(ans+res)%mod;
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])
            swap(x,y);
        res=0;
        Wque(1,1,n,id[x],id[y]);
        ans=(ans+res)%mod;
        return ans;
    }
    void Wupdr(int x,int y,int k)
    {
        k%=mod;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            Wupd(1,1,n,id[top[x]],id[x],k);
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])
            swap(x,y);
        Wupd(1,1,n,id[x],id[y],k);
    }
    int Wqs(int x)
    {
        res=0;
        Wque(1,1,n,id[x],id[x]+siz[x]-1);
        return res;
    }
    void Wupds(int x,int k)
    {
        Wupd(1,1,n,id[x],id[x]+siz[x]-1,k);
    }
    void Wdfs1(int u,int f,int deep)
    {
        dep[u]=deep,fa[u]=f,siz[u]=1;
        int maxson=-1;
        for(int i=hh[u];i;i=ne[i])
        {
            int v=to[i];
            if(v==f)
                continue;
            Wdfs1(v,u,deep+1);
            siz[u]+=siz[v];
            if(siz[v]>maxson)
                son[u]=v,maxson=siz[v];
        }
    }
    void Wdfs2(int u,int topf)
    {
        id[u]=++tot,top[u]=topf;
        wt[tot]=w[u];
        if(!son[u])
            return;
        Wdfs2(son[u],topf);
        for(int i=hh[u];i;i=ne[i])
        {
            int v=to[i];
            if(v==fa[u]||v==son[u])
                continue;
            Wdfs2(v,v);
        }
    }
    short main()
	{
        n=qr,m=qr,r=qr,mod=qr;
        fo(i,1,n)
            w[i]=qr;
        fo(i,1,n-1)
        {
            int a=qr,b=qr;
            Wadd(a,b),Wadd(b,a);
        }
        Wdfs1(r,0,1),Wdfs2(r,r);
        Wbuild(1,1,n);
        while(m--)
        {
            int op=qr;
            if(op==1)
            {
                int x=qr,y=qr,z=qr;
                Wupdr(x,y,z);
            }
            else if(op==2)
            {
                int x=qr,y=qr;
                printf("%d\n",Wqr(x,y));
            }
            else if(op==3)
            {
                int x=qr,y=qr;
                Wupds(x,y);
            }
            else
            {
                int x=qr;
                printf("%d\n",Wqs(x));
            }
        }
		return Ratio;
	}
}
int main(){eturn Wisadel::main();}

例题 P2590 [ZJOI2008] 树的统计

题链Luogu 题库

主要题干:
image

把这个题当做例题细讲。

因为这个题只有单点修改,区间求值,属于最好写的线段树类型,所以方便用来理解树链剖分。

LCA 部分没有变化,跳过。

线段树部分很基础,跳过。

不过还是放个代码

DFS 部分

    void Wdfs1(int u,int f,int deep)
    {
        dep[u]=deep,fa[u]=f,siz[u]=1;
        for(int i=hh[u];i!=-1;i=ne[i])
        {
            int v=to[i];
            if(v==f)
                continue;
            Wdfs1(v,u,deep+1);
            siz[u]+=siz[v];
            if(siz[v]>siz[son[u]])
                son[u]=v;
        }
    }
    void Wdfs2(int u,int topf)
    {
        id[u]=++tot,top[u]=topf;
        wt[tot]=w[u];
        if(!son[u])
            return;
        Wdfs2(son[u],topf);
        for(int i=hh[u];i!=-1;i=ne[i])
        {
            int v=to[i];
            if(v==fa[u]||v==son[u])
                continue;
            Wdfs2(v,v);
        }
    }

线段树部分

    #define ls (rt<<1)
    #define rs (rt<<1|1)
    #define mid ((l+r)>>1)
    void Wpushup(int rt)
    {
        tsum[rt]=tsum[ls]+tsum[rs];
        tmax[rt]=max(tmax[ls],tmax[rs]);
    }
    void Wbuild(int rt,int l,int r)
    {
        if(l==r)
        {
            tsum[rt]=tmax[rt]=wt[l];
            return;
        }
        Wbuild(ls,l,mid);
        Wbuild(rs,mid+1,r);
        Wpushup(rt);
    }
    void Wupd(int rt,int l,int r,int x,int k)
    {
        if(l==r)
        {
            tsum[rt]=tmax[rt]=k;
            return;
        }
        if(x<=mid)
            Wupd(ls,l,mid,x,k);
        else
            Wupd(rs,mid+1,r,x,k);
        Wpushup(rt);
    }
    int Wquesum(int rt,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
            return tsum[rt];
        int ans=0;
        if(x<=mid)
            ans+=Wquesum(ls,l,mid,x,y);
        if(y>mid)
            ans+=Wquesum(rs,mid+1,r,x,y);
        return ans;
    }
    int Wquemax(int rt,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
            return tmax[rt];
        int ans=-maxi;
        if(x<=mid)
            ans=max(ans,Wquemax(ls,l,mid,x,y));
        if(y>mid)
            ans=max(ans,Wquemax(rs,mid+1,r,x,y));
        return ans;
    }
    #undef ls
    #undef rs
    #undef mid

主要看看转链的部分。

首先当所求区间不在一个链上,我们找一条更深的链,求出该链上的答案,然后不断跳至查找完链的顶部的父亲,重复操作至区间在一条链上,然后直接查询这个区间的答案即可。

代码如下:

    int Wqsum(int x,int y)
    {
        int ans=0;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            //找深的那条
            ans+=Wquesum(1,1,n,id[top[x]],id[x]);
            //记录这条链上的答案
            x=fa[top[x]];
            //调到链顶的父亲
        }
        if(dep[x]<dep[y])
            swap(x,y);
        //找编号小的
        ans+=Wquesum(1,1,n,id[y],id[x]);
        return ans;
    }
    int Wqmax(int x,int y)
    {//同上
        int ans=-maxi;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            ans=max(ans,Wquemax(1,1,n,id[top[x]],id[x]));
            x=fa[top[x]];
        }
        if(dep[x]<dep[y])
            swap(x,y);
        ans=max(ans,Wquemax(1,1,n,id[y],id[x]));
        return ans;
    }

主函数十分简单,就不放了。

可以看出,树链剖分的关键就在于上述查询转链的步骤,主要思想是 LCA + 区间查询。


(未)完结撒花~

image

posted @ 2024-05-15 12:13  DrRatio  阅读(24)  评论(0编辑  收藏  举报