树链剖分学习笔记

写代码又犯了很sb的错误,线段树写错了。。。
好像每次都会把r-l+1写成l-r+1,然后就只有20分。。。
代码写的比较丑,压了压之后190行。。。
基本上是我打过的最长的一个模板了
然后简单介绍一下树剖吧。。。


树链剖分,就是把树剖分成链,然后用数据结构来维护这些链,使得询问、修改的复杂度达到 O(logn) O ( l o g n ) (不会证明。。。)
几个定义:

  • 重儿子:当前节点子节点里子树规模最大的节点
  • 轻儿子:当前节点子节点里除重儿子外的子节点
  • 重边:链接重儿子与其父节点的边
  • 轻边:其他边
  • 重链:由重链连接出的路径
  • 轻链:由轻链连接出的路径
    然后,我们有两次dfs,第一遍dfs处理出每个点的父节点、深度、子树规模(包括自己)、重儿子,第二次dfs处理出每条重链上的起始点、dfs的顺序编号(优先dfs重儿子)、dfs顺序编号对应的树上编号。
    这样dfs结束之后

每条重链上的点的dfs编号是连续的,一个点和它的子树中的点的dfs编号也是连续的

这样我们就可以利用这个性质来把树上的修改、查询问题转化成区间问题,就可以用树状数组、线段树等数据结构来维护,进行修改、查询等操作
当然其中也包含LCA问题,这里并不是用倍增求的LCA,而是用剖分好的链来求的
好像这样剖分时间复杂度会比较有保证,但是我并不会证明。。。好弱啊。。。
还有一些小细节问题,可以看代码

我是看这里学的树剖:
https://www.luogu.org/blog/communist/shu-lian-pou-fen-yang-xie

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define For(i,l,r) for(int i=l;i<=r;++i)
#define mid ((l+r)>>1)
#define ls (x<<1)
#define rs ((x<<1)|1)
#define MAXN 100010
using namespace std;
inline int read()
{
    char c;bool t=0;int a=0;
    while((c=getchar())==' '||c=='\n'||c=='\r');
    if(c=='-'){t=1;c=getchar();}
    while(isdigit(c)){a*=10;a+=(c-'0');c=getchar();}
    return a*(t?-1:1);
}
struct Edge{
    int to,next;
}e[MAXN*2];
int n,m,root,p,cnt;
int v[MAXN],last[MAXN],fa[MAXN],dep[MAXN],tid[MAXN],dfs[MAXN],son[MAXN],top[MAXN],size[MAXN];
int lazy[MAXN*4],sum[MAXN*4],ans;
void dfs1(int now,int fat,int depth)
{
    dep[now]=depth;fa[now]=fat;size[now]=1;
    int temp=last[now];
    while(temp)
    {
        if(e[temp].to!=fat)
        {
            dfs1(e[temp].to,now,depth+1);
            size[now]+=size[e[temp].to];
            if(size[e[temp].to]>size[son[now]])son[now]=e[temp].to;
        }
        temp=e[temp].next;
    }
}
void dfs2(int now,int t)
{
    top[now]=t;dfs[now]=++cnt;tid[cnt]=now;
    if(!son[now])return;
    dfs2(son[now],t);
    int temp=last[now];
    while(temp)
    {
        if(e[temp].to!=fa[now]&&e[temp].to!=son[now])dfs2(e[temp].to,e[temp].to);
        temp=e[temp].next;
    }
}
void build(int l,int r,int x)
{
    if(l==r)
    {
        sum[x]=v[tid[l]];
        sum[x]%=p;
    }
    else
    {
        build(l,mid,ls);
        build(mid+1,r,rs);
        sum[x]=sum[ls]+sum[rs];sum[x]%=p;
    }
}
void down(int l,int r,int x)
{
    sum[ls]+=(lazy[x]*(mid-l+1))%p;sum[ls]%=p;
    sum[rs]+=(lazy[x]*(r-mid))%p;sum[rs]%=p;
    lazy[ls]+=lazy[x];lazy[ls]%=p;
    lazy[rs]+=lazy[x];lazy[rs]%=p;
    lazy[x]=0;
}
void modify(int l,int r,int x,int ll,int rr,int k)
{
    if(ll<=l&&r<=rr)
    {
        lazy[x]+=k;lazy[x]%=p;
        sum[x]+=((r-l+1)*k)%p;sum[x]%=p;
    }
    else
    {
        if(lazy[x])down(l,r,x);
        if(ll<=mid)modify(l,mid,ls,ll,rr,k);
        if(mid<rr)modify(mid+1,r,rs,ll,rr,k);
        sum[x]=sum[ls]+sum[rs];
    }
}
void ask(int l,int r,int x,int ll,int rr)//线段树询问
{
    if(ll<=l&&r<=rr)
    {
        ans+=sum[x];ans%=p;
    }
    else
    {
        if(lazy[x])down(l,r,x);
        if(ll<=mid)ask(l,mid,ls,ll,rr);
        if(mid<rr)ask(mid+1,r,rs,ll,rr);
    }
}
void query(int x,int y)//
{
    ans=0;
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]>=dep[fy])
        {
            ask(1,n,1,dfs[fx],dfs[x]);
            x=fa[fx];fx=top[x];
        }
        else
        {
            ask(1,n,1,dfs[fy],dfs[y]);
            y=fa[fy];fy=top[y];
        }
    }
    if(dfs[x]<=dfs[y])ask(1,n,1,dfs[x],dfs[y]);
    else ask(1,n,1,dfs[y],dfs[x]);
    printf("%d\n",ans);
}
void update(int x,int y,int z)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]>=dep[fy])
        {
            modify(1,n,1,dfs[fx],dfs[x],z);
            x=fa[fx];fx=top[x];
        }
        else
        {
            modify(1,n,1,dfs[fy],dfs[y],z);
            y=fa[fy];fy=top[y];
        }
    }
    if(dfs[x]<=dfs[y])
     modify(1,n,1,dfs[x],dfs[y],z);
    else
     modify(1,n,1,dfs[y],dfs[x],z);
}
void add(int from,int to)
{
    e[++cnt].next=last[from];
    e[cnt].to=to;
    last[from]=cnt;
}
int main()
{
    int tx,ty,tz;
    n=read();m=read();root=read();p=read();
    For(i,1,n)
     v[i]=read();
    for(int i=1;i<n;++i)
    {
        tx=read();ty=read();
        add(tx,ty);add(ty,tx);
    }
    dfs1(root,0,1);cnt=0;dfs2(root,root);build(1,n,1);
    For(i,1,m)
    {
        tx=read();
        if(tx==1)
        {
            tx=read();ty=read();tz=read();
            update(tx,ty,tz%p);
        }
        else if(tx==2)
        {
            tx=read();ty=read();
            query(tx,ty);
        }
        else if(tx==3)
        {
            tx=read();ty=read();
            modify(1,n,1,dfs[tx],dfs[tx]+size[tx]-1,ty%p);
        }
        else
        {
            tx=read();ans=0;
            ask(1,n,1,dfs[tx],dfs[tx]+size[tx]-1);//修改子树,序号是连续的
            printf("%d\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值