树链剖分复习笔记

鸽了好久的一篇博客啊....

题目描述

如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

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

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

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

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

输入输出格式

输入格式:

 

第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。

接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)

接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:

操作1: 1 x y z

操作2: 2 x y

操作3: 3 x z

操作4: 4 x

 

输出格式:

 

输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)

 

输入输出样例

输入样例#1: 
5 5 2 24
7 3 7 8 0 
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
输出样例#1: 
2
21

说明

时空限制:1s,128M

数据规模:

对于30%的数据: N10,M10

对于70%的数据: N103,M103

对于100%的数据: N105,M105

( 其实,纯随机生成的树LCA+暴力是能过的,可是,你觉得可能是纯随机的么233 )

样例说明:

树的结构如下:

 各个操作如下:

(之前一直觉得树剖非常难来着...)

树链剖分,就是把一颗树分成链们,然后用数据结构维护值.

总的来说难点就是两个dfs。

一、原理

把一棵树分成链和轻链,然后用数据结构维护。

二、几组概念

 重边:父节点和他重儿子的连边

      重儿子:对于非叶节点,他的儿子中以那个儿子为根的子树节点数最大的儿子为重儿子

      轻边:除重边,其余全是轻边

      轻儿子:每个非叶节点的儿子中,除去重儿子,其余全是轻儿子

      重链:当一条链全为重边组成,其为重链。

      注意:

  • 对于叶节点,若其为轻儿子,则有一条以自己为起点的长度为一的链。
  • 每一条重链均以轻儿子为起点,即为下面提到的TOP。
  • 叶节点既没有重儿子,也没有轻儿子,因为他没有儿子。。。
  • 每条边的值其实就是进行DFS时的序号。

     如图

为什么呢?重链的话,我们当然希望越简单维护的东西越挨在一起,于是越长链我们把他们鸽在一起(咕咕咕)

然后把它们用线段树啊treap啊之类的维护一下就好了。

三、实现手段

(1)变量申明

struct edge
{
    int to,next;
}e[maxn];
int head[maxn],cnt;
inline int addedge(int from,int to)//初步存图(树)
{
    e[++cnt].next=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
int rt=0;
int son[maxn];//节点的重儿子 
int size[maxn];//子树大小 
int top[maxn];//重链的顶端 
int dep[maxn];//深度
int dfsn[maxn];//dfs序 
int fa[maxn];//点的父亲 
int newid[maxn];//新的点在线段树里的下标 
int tot;//节点数量
struct tree//线段树
{
    int l,r,ls,rs,pre,add;
}t[maxn];

 

要实现标记轻儿子,重儿子,我们需要子树大小,这需要dfs,深度,这也要dfs,dfs序....反正就是dfs就是了

所以

(2)dfs

void dfs1(int u)//处理的量:子树大小,深度,fa,son
{
    size[u]=1;//首先子树大小为1(自身)
    dep[u]=dep[fa[u]]+1;//同lca预处理
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa[u])//向下遍历
        {
            fa[v]=u;
            dfs1(v);//先向下
            size[u]+=size[v];//再统计子树大小
            if(size[son[u]]<size[v])////
            son[u]=v;//保存重儿子
        }
    }
}

dfs1还是较好理解&&实现的跑完这样:

 

个人认为难点是dfs2

void dfs2(int u,int d)//当前点,和链顶//处理量:链顶,dfs序,新编号
{
    top[u]=d;
    dfsn[u]=++tot;//记录dfs序
    newid[tot]=u;//线段树里的东西
    if(son[u])//如果有重儿子
    dfs2(son[u],d);//继续走重儿子伸长重链
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=son[u]&&v!=fa[u])//如果v是轻儿子
        dfs2(v,v);//那跟新链顶,继续向下
    }
}

其实到这树剖就已经差不多了跑完这样:

 

(3)数据结构

以线段树为例:

inline void pushup(int p)
{
    t[p].pre=(t[t[p].ls].pre+t[t[p].rs].pre)%mod;//向下跟新值
}

void build(int l,int r,int p)//普普通通的建树
{
    if(l==r)
    {
        t[p].pre=a[newid[l]];
        t[p].l=t[p].r=l;//
        return ;
    }
    int mid=l+r>>1;
    t[p].ls=tot++;
    t[p].rs=tot++;
    build(l,mid,t[p].ls);
    build(mid+1,r,t[p].rs);
    t[p].l=t[t[p].ls].l;//坑点在这里,普通线段树可以用<<1|1什么的找到儿子,但是因为树剖的特殊性,这里不行(个人认为这样要更方便)
    t[p].r=t[t[p].rs].r;
    pushup(p);//加值
}
inline int len(int p)
{
    return t[p].r-t[p].l+1;
}
void spread(int p)
{
    if(t[p].add)//下传标记
    {
        int ls=t[p].ls,rs=t[p].rs,lz=t[p].add;
        t[ls].add=(t[ls].add+lz)%mod;
        t[rs].add=(t[rs].add+lz)%mod;
        t[ls].pre=(t[ls].pre+lz*len(ls))%mod;
        t[rs].pre=(t[rs].pre+lz*len(rs))%mod;
        t[p].add=0;
    }
}
void change(int l,int r,int k,int p)//普普通通的区间加
{
    if(l<=t[p].l&&r>=t[p].r)
    {
        t[p].pre=(t[p].pre+k*(t[p].r-t[p].l+1))%mod;
        t[p].add=(t[p].add+k)%mod;
        return;
    }
    spread(p);
    int mid=t[p].l+t[p].r>>1;
    if(l<=mid)change(l,r,k,t[p].ls);
    if(r>mid) change(l,r,k,t[p].rs);
    pushup(p);
}
int ask(int l,int r,int p)//普普通通的区间查
{
    if(t[p].l>=l&&t[p].r<=r)
        return t[p].pre%mod;
    spread(p);
    int mid=t[p].l+t[p].r>>1,res=0;
    if(mid>=l)res=(res+ask(l,r,t[p].ls))%mod;
    if(mid<r) res=(res+ask(l,r,t[p].rs))%mod;
    return res%mod;
}

 这样就处理好了第三第四操作~

下面,又是一个难点:

操作1和操作2.这里的处理方式有点像lca....

inline int sum(int x,int y)
{
    int ret=0;
    while(top[x]!=top[y])//一直走到顶
    {
        if(dep[top[x]]<dep[top[y]])
        swap(x,y);
        ret=(ret+ask(dfsn[top[x]],dfsn[x],rt)%mod);//可以区间查的区间查
        x=fa[top[x]];//跳上去
    }
//循环之后,这两个点在同一重链上,但由于不知道是否是同一点,所以来统计一下两点的贡献
if(dfsn[x]>dfsn[y]) swap(x,y); return (ret+ask(dfsn[x],dfsn[y],rt))%mod; } inline void updates(int x,int y,int c) { while(top[x]!=top[y])//同上,能区间加的区间加 { if(dep[top[x]]<dep[top[y]]) swap(x,y); change(dfsn[top[x]],dfsn[x],c,rt); x=fa[top[x]];//跳上去 } if(dfsn[x]>dfsn[y]) swap(x,y); change(dfsn[x],dfsn[y],c,rt); }

于是,树剖基本就结束了(真的结束了)

完整代码(模板题):

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e6+10;
int n,m,r,mod;
int a[maxn];
struct edge
{
    int to,next;
}e[maxn];
int head[maxn],cnt;
inline int addedge(int from,int to)
{
    e[++cnt].next=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
int rt=0;
int son[maxn];//节点的儿子 
int size[maxn];//子树大小 
int top[maxn];//重链的顶端 
int dep[maxn];//深度
int dfsn[maxn];//dfs序 
int fa[maxn];//点的父亲 
int newid[maxn];//新的点在线段树里的下标 
int tot;//|ì??ਤo? 
struct tree
{
    int l,r,ls,rs,pre,add;
}t[maxn];
void dfs1(int u)
{
    size[u]=1;
    dep[u]=dep[fa[u]]+1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa[u])
        {
            fa[v]=u;
            dfs1(v);
            size[u]+=size[v];
            if(size[son[u]]<size[v])////
            son[u]=v;
        }
    }
}
void dfs2(int u,int d)
{
    top[u]=d;
    dfsn[u]=++tot;
    newid[tot]=u;
    if(son[u])
    dfs2(son[u],d);
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=son[u]&&v!=fa[u])
        dfs2(v,v);
    }
}

inline void pushup(int p)
{
    t[p].pre=(t[t[p].ls].pre+t[t[p].rs].pre)%mod;
}

void build(int l,int r,int p)
{
    if(l==r)
    {
        t[p].pre=a[newid[l]];
        t[p].l=t[p].r=l;
        return ;
    }
    int mid=l+r>>1;
    t[p].ls=tot++;
    t[p].rs=tot++;
    build(l,mid,t[p].ls);
    build(mid+1,r,t[p].rs);
    t[p].l=t[t[p].ls].l;
    t[p].r=t[t[p].rs].r;
    pushup(p);
}
inline int len(int p)
{
    return t[p].r-t[p].l+1;
}
void spread(int p)
{
    if(t[p].add)
    {
        int ls=t[p].ls,rs=t[p].rs,lz=t[p].add;
        t[ls].add=(t[ls].add+lz)%mod;
        t[rs].add=(t[rs].add+lz)%mod;
        t[ls].pre=(t[ls].pre+lz*len(ls))%mod;
        t[rs].pre=(t[rs].pre+lz*len(rs))%mod;
        t[p].add=0;
    }
}
void change(int l,int r,int k,int p)
{
    if(l<=t[p].l&&r>=t[p].r)
    {
        t[p].pre=(t[p].pre+k*(t[p].r-t[p].l+1))%mod;
        t[p].add=(t[p].add+k)%mod;
        return;
    }
    spread(p);
    int mid=t[p].l+t[p].r>>1;
    if(l<=mid)change(l,r,k,t[p].ls);
    if(r>mid) change(l,r,k,t[p].rs);
    pushup(p);
}
int ask(int l,int r,int p)
{
    if(t[p].l>=l&&t[p].r<=r)
        return t[p].pre%mod;
    spread(p);
    int mid=t[p].l+t[p].r>>1,res=0;
    if(mid>=l)res=(res+ask(l,r,t[p].ls))%mod;
    if(mid<r) res=(res+ask(l,r,t[p].rs))%mod;
    return res%mod;
}
inline int sum(int x,int y)
{
    int ret=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])
        swap(x,y);
        ret=(ret+ask(dfsn[top[x]],dfsn[x],rt)%mod);
        x=fa[top[x]];
    }
    if(dfsn[x]>dfsn[y])
    swap(x,y);
    return (ret+ask(dfsn[x],dfsn[y],rt))%mod;
}
inline void updates(int x,int y,int c)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])
        swap(x,y);
        change(dfsn[top[x]],dfsn[x],c,rt);
        x=fa[top[x]];
    }
    if(dfsn[x]>dfsn[y])
    swap(x,y);
    change(dfsn[x],dfsn[y],c,rt);
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&r,&mod);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    dfs1(r);
    dfs2(r,r);
    tot=0;
    build(1,n,rt=tot++);
    for(int i=1;i<=m;i++)
    {
        int flag,x,y,z;
        scanf("%d",&flag);
        if(flag==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            updates(x,y,z);
        }
        if(flag==2)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",sum(x,y)%mod);
        }
        if(flag==3)
        {
            scanf("%d%d",&x,&z);
            change(dfsn[x],dfsn[x]+size[x]-1,z,rt);
        }
        if(flag==4)
        {
            scanf("%d",&x);
            printf("%d\n",ask(dfsn[x],dfsn[x]+size[x]-1,rt)%mod);
        }
    }
    return 0;
}

 (完)

图片来源:https://www.cnblogs.com/2529102757ab/p/10732188.html

转载于:https://www.cnblogs.com/ajmddzp/p/11313562.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值