树链剖分剖后感

1、我的脑子要被剖开啦。
2、辣鸡教材!
3、抱紧大佬博客才是最好的!
当然了,思想是在教材上看的,但是教材上一条重链一个线段树真的是……emmmm
然后就参考了正确的入门姿势,成功过了洛谷上的模板题
树链剖分,就是将树拆成一小段一小段连续的链,得到线性的数据,但这个线性的数据使用是有限制的,比如跨段查询或修改的时候就会出错,那就只能一段一段处理,然后再合并。这时要尽量使每段更长些,那么就可以将任务尽可能多的交给线段树处理,这样复杂度是log级别的。
通过两次dfs将整颗树分成轻链和重链(同一重链在线段树上是连续的),这样将树拆成一段一段连续的链然后放到线段树上去,当查询两个节点之间的东西时,通过top[]数组和fa[]数组将这两个节点向上挪动,每次将deep[]较深的b移到fa[top[b]]处(此过程中顺便处理top[b]->b在线段树上值),重复此过程直到两个节点的top[]相同,top[]相同就意味着此时这两个节点在同一重链上,此时再查找一次(同一重链在线段树上是连续的)。

处理以节点x为根的整个子树:用线段树处理[pos[x],pos[x]+siz[x]-1]即可
查询节点x和节点y的LCA(最近公共祖先):将两点不断向上挪动并处理,直到两点的top[]相同,deep[]小的点即是最近公共祖先
处理节点x到节点y的路径上的点:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[pos[x],pos[y]]即可
处理节点x到节点y的路径上的边:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[pos[son[x]],pos[y]]即可(用每个点表示通向其父节点的边)
以洛谷模板题为例给出代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
struct line{
    int y,next;
}l[MAXN<<1];
int n,m,r,p,Left,Right,x,y,z,type,cnt=0;
int fa[MAXN],deep[MAXN],son[MAXN],siz[MAXN],fi[MAXN];
int top[MAXN],pos[MAXN],num=0,in[MAXN],temp[MAXN];
int t[MAXN<<2],delta[MAXN<<2];
int read(){
    int num=0;char c=getchar();
    for(;c<'0'||c>'9';c=getchar());
    for(;c>='0'&&c<='9';c=getchar())
        num=(num<<3)+(num<<1)+c-48;
    return num;
}
void make_line(int x,int y){
    l[++cnt].y=y;l[cnt].next=fi[x];fi[x]=cnt;
    l[++cnt].y=x;l[cnt].next=fi[y];fi[y]=cnt;
}

void dfs(int x,int f,int d){//第一次dfs,寻找重儿子
    fa[x]=f;deep[x]=d;
    son[x]=0;siz[x]=1;
    for(int i=fi[x];i;i=l[i].next){
        int v=l[i].y;
        if(v==f)continue;
        dfs(v,x,d+1);
        siz[x]+=siz[v];
        if(siz[v]>siz[son[x]])son[x]=v;
    }
}

void dfs2(int x,int tp){//第二次dfs,构造重链,记录节点在线段树上的位置
    top[x]=tp;pos[x]=++num;
    if(son[x])dfs2(son[x],tp);
    for(int i=fi[x];i;i=l[i].next){
        int v=l[i].y;
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}

void make_tree(int l,int r,int root){//初始化
    if(l==r){
        t[root]=in[l];
        return;
    }
    int mid=(l+r)>>1;
    make_tree(l,mid,root<<1);
    make_tree(mid+1,r,root<<1|1);
    t[root]=t[root<<1]+t[root<<1|1];
}

void push_down(int l,int r,int root){//下移delta
    int mid=(l+r)>>1;
    delta[root<<1]+=delta[root];delta[root<<1|1]+=delta[root];
    delta[root<<1]%=p;delta[root<<1|1]%=p;
    t[root<<1]=(t[root<<1]+delta[root]*(mid-l+1))%p;
    t[root<<1|1]=(t[root<<1|1]+delta[root]*(r-mid))%p;
    delta[root]=0;
}

void add(int l,int r,int root){//线段树模板
    if(r<Left||l>Right)return;
    if(Left<=l&&Right>=r){
        delta[root]+=z;
        delta[root]%=p;
        t[root]=(t[root]+z*(r-l+1))%p;
        return;
    }
    int mid=(l+r)>>1;
    push_down(l,r,root);
    add(l,mid,root<<1);
    add(mid+1,r,root<<1|1);
    t[root]=t[root<<1]+t[root<<1|1];
    return;
}

long long find(int l,int r,int root){//模板<<1
    if(r<Left||l>Right)return 0;
    if(Left<=l&&Right>=r)return t[root];
    int mid=(l+r)>>1;
    push_down(l,r,root);
    return (find(l,mid,root<<1)+find(mid+1,r,root<<1|1))%p;
}

void change(){
    int a=read(),b=read();z=read();
    int ta=top[a],tb=top[b];
    while(ta!=tb){
        if(deep[ta]>deep[tb]){
            swap(ta,tb);
            swap(a,b);
        }
        Left=pos[tb];Right=pos[b];
        add(1,n,1);//分段处理
        b=fa[tb];tb=top[b];
    }
    if(deep[a]>deep[b])swap(a,b);
    Left=pos[a];
    Right=pos[b];
    add(1,n,1);//同一重链后的处理
}

void query(){
    int a=read(),b=read();
    int ta=top[a],tb=top[b];
    long long ans=0;
    while(ta!=tb){
        if(deep[ta]>deep[tb]){
            swap(ta,tb);
            swap(a,b);
        }
        Left=pos[tb];Right=pos[b];
        ans+=find(1,n,1);//同上
        ans%=p;
        b=fa[tb];tb=top[b];
    }
    if(deep[a]>deep[b])swap(a,b);
    Left=pos[a];
    Right=pos[b];
    ans+=find(1,n,1);
    ans%=p;
    printf("%lld\n",ans);
}

void work(){
    n=read();m=read();r=read();p=read();
    for(int i=1;i<=n;++i)
        temp[i]=read();
    for(int i=1;i<n;++i){
        x=read();y=read();
        make_line(x,y);
    }
    dfs(r,r,1);
    dfs2(r,r);
    for(int i=1;i<=n;++i)
        in[pos[i]]=temp[i];
    make_tree(1,n,1);
    for(int i=1;i<=m;++i){
        type=read();
        if(type==1)change();
        if(type==2)query();
        if(type==3){
            x=read(),z=read();
            Left=pos[x];Right=Left+siz[x]-1;//注意子树的操作
            add(1,n,1);
        }
        if(type==4){
            x=read();
            Left=pos[x];Right=Left+siz[x]-1;
            printf("%lld\n",find(1,n,1));
        }
    }
}

int main(){
    work();
    return 0;
}

模板先到这里,还有很多题要做……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值