树链剖分基础入门 -- 强大的树上操纵数据结构

树链剖分

对树进行重新编号,使得能将任意一条路径,变为o(logn)段连续区间。

  • 重儿子:子树中节点数最多的根节点是重儿子。
  • 其余点为轻儿子
  • 重边:重儿子与父节点的边。
  • 重链:极大的重边构成的路径

重儿子放置到父节点的重链中

轻儿子放置到子节点的重链中

在这里插入图片描述

dfs序:优先遍历重儿子,即可保证重链上的所有的编号是连续的。

定理:树中任意一条路径都可以拆分为O(logn)条重链,即可拆分成O(logn)个连续区间。

算法流程:dfs标记重儿子,然后dfs2找到重链

时间复杂度:上界是logn。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
#define il (id<<1)
#define ir il+1
ll w[maxn],nw[maxn];
int n,m;
int sz[maxn],fa[maxn],son[maxn],dep[maxn],top[maxn];
int nid[maxn],cnt;
struct tree
{
    int l,r;
    ll lz,sum;
}tr[maxn<<2];
vector<int>e[maxn];
void dfs1(int s,int father,int depth)
{
    dep[s]=depth;
    fa[s]=father;sz[s]=1;
    for(auto to:e[s])
    {
        if(to==father)continue;
        dfs1(to,s,depth+1);
        sz[s]+=sz[to];
        if(sz[to]>sz[son[s]])son[s]=to;
    }
}
void dfs2(int s,int t)//用来放重链的起点,根据定理重链的起点是轻链。
{
    nid[s]=++cnt,nw[cnt]=w[s],top[s]=t;
    if(!son[s])return ;//没有重链表示是叶子节点
    dfs2(son[s],t);
    for(auto to:e[s])//遍历轻儿子
    {
        if(to==fa[s]||to==son[s])continue;
        dfs2(to,to);
    }
}
void pushdown(int id)
{
    if(tr[id].lz!=0)
    {
        tr[il].lz+=tr[id].lz;tr[il].sum+=tr[id].lz*(tr[il].r-tr[il].l+1);
        tr[ir].lz+=tr[id].lz;tr[ir].sum+=tr[id].lz*(tr[ir].r-tr[ir].l+1);
        tr[id].lz=0;
    }
}
void pushup(int id)
{
    tr[id].sum=tr[il].sum+tr[ir].sum;
}
void build(int id,int l,int r)
{
    tr[id]={l,r,0,0};
    if(l==r)
    {
        tr[id].sum=nw[r];
        return ;
    }
    int mid=(l+r)>>1;
    build(il,l,mid);
    build(ir,mid+1,r);
    pushup(id);
}
void update(int id,int l,int r,ll y)
{
    if(tr[id].l>=l&&tr[id].r<=r)
    {
        tr[id].lz+=y;
        tr[id].sum+=(tr[id].r-tr[id].l+1)*y;
        return ;
    }
    pushdown(id);
    if(tr[il].r>=l)update(il,l,r,y);
    if(tr[ir].l<=r)update(ir,l,r,y);
    pushup(id);
}
ll querry(int id,int l,int r)
{
    if(tr[id].l>=l&&tr[id].r<=r)
    {
        return tr[id].sum;       
    }
    pushdown(id);
    ll ans=0;
    if(tr[il].r>=l)ans+=querry(il,l,r);
    if(tr[ir].l<=r)ans+=querry(ir,l,r);
    return ans;
}
void update_path(int u,int v,ll k)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        update(1,nid[top[u]],nid[u],k);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v])swap(u,v);
    update(1,nid[u],nid[v],k);
}
void update_tree(int u,ll k)
{
    update(1,nid[u],nid[u]+sz[u]-1,k);
}
ll querry_path(int u,int v)
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        ans+=querry(1,nid[top[u]],nid[u]);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v])swap(u,v);
    ans+=querry(1,nid[u],nid[v]);
    return ans;
}
ll querry_tree(int u)
{
    return querry(1,nid[u],nid[u]+sz[u]-1);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,-1,1);
    dfs2(1,1);
    build(1,1,n);
    int q;
	scanf("%d",&q);
    while(q--)
    {
        int op;
        scanf("%d",&op);
        if(op==1)
        {
            int u,v,k;
            scanf("%d%d%d",&u,&v,&k);
            update_path(u,v,k);
        }
        else if(op==2)
        {
            int u,k;
            scanf("%d%d",&u,&k);
            update_tree(u,k);
        }
        else if(op==3)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%lld\n",querry_path(u,v));
        }
        else 
        {
            int u;
            scanf("%d",&u);
            printf("%lld\n",querry_tree(u));
        }
    }
    return 0;
}

例题 2022年杭电多校第二场1001

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值