树链剖分

这显然是一个很高级(???)的算法,关于原理等东东这里将不会赘述;

这里主要是给出一些代码细节(这个模板写了我1.5h,中间犯了几个低级错误)

洛谷的模板链接

我的代码如下(自认为码风优良QwQ):

#include<stdio.h>
#include<iostream>
#define maxn 100010
#define maxm 200010
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define int long long
//骚操作QAQ,但似乎没必要
using namespace std;

int bg[maxn],nt[maxm],to[maxm],e;//链式前向星
int dep[maxn],f[maxn],siz[maxn],son[maxn];
int id[maxn],cnt,top[maxn],w[maxn];//树剖的dfs1,dfs2所需变量
int s[maxn<<2],p[maxn<<2];//线段树
int n,m,rt,mod,a[maxn];

void insert(int x,int y) {
    nt[++e]=bg[x];
    to[e]=y;
    bg[x]=e;
}

void dfs1(int x,int fa,int deep) {
    int i,u,mx=-1;
    dep[x]=deep;
    f[x]=fa;
    siz[x]=1;
    for (i=bg[x];i;i=nt[i]) {
        u=to[i];
        if (u==fa) continue;//千万别写成return了QAQ
        dfs1(u,x,deep+1);
        siz[x]+=siz[u];//回溯统计
        if (siz[u]>mx) {
            mx=siz[u];
            son[x]=u;
        }
    }
}//dfs1:预处理各节点深度dep[x],父亲f[x],子树大小(包含自身)siz[x],重儿子son[x];

void dfs2(int x,int topfa) {//topfa记录当前重链的起始点(链上深度最小的)
    int i,u;
    id[x]=++cnt;
    w[cnt]=a[x]%mod;
    top[x]=topfa;
    if (!son[x]) return ;//到了叶子
    dfs2(son[x],topfa);//先拓展重儿子
    for (i=bg[x];i;i=nt[i]) {
        u=to[i];
        if (u==f[x] || u==son[x]) continue;//拓展不是父亲也不是重儿子的子节点
        dfs2(u,u);
    }
}//dfs2:预处理各节点新编号id[x],新编号所对应点权w[x_new],以及该节点所在重链的起始点;

//以下线段树部分:

void push_down(int rt,int l,int r) {
    if (p[rt]) {
        p[rt<<1]=(p[rt<<1]+p[rt])%mod;
        p[rt<<1|1]=(p[rt<<1|1]+p[rt])%mod;
        s[rt<<1]=(s[rt<<1]+p[rt]*(mid-l+1))%mod;
        s[rt<<1|1]=(s[rt<<1|1]+p[rt]*(r-mid))%mod;
        p[rt]=0;
    }
}

void build(int rt,int l,int r) {
    if (l==r) {
        s[rt]=w[l];
        return ;
    }
    build(lson);
    build(rson);
    s[rt]=(s[rt<<1]+s[rt<<1|1])%mod;
}

void update(int rt,int l,int r,int L,int R,int pos) {
    if (L<=l && r<=R) {
        p[rt]=(p[rt]+pos)%mod;
        s[rt]=(s[rt]+pos*(r-l+1))%mod;
        return ;
    }
    push_down(rt,l,r);
    if (L<=mid) update(lson,L,R,pos);
    if (R>mid) update(rson,L,R,pos);
    s[rt]=(s[rt<<1]+s[rt<<1|1])%mod;
}

int query(int rt,int l,int r,int L,int R) {
    if (L<=l && r<=R) {
        return s[rt];
    }
    push_down(rt,l,r);
    int res=0;
    if (L<=mid) res=query(lson,L,R)%mod;
    if (R>mid) res=(res+query(rson,L,R))%mod;
    return res%mod;
}

//以上是对于新编号的点用线段树维护(四步走)

void updRange(int x,int y,int z) {
    while (top[x]!=top[y]) {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,1,n,id[top[x]],id[x],z);//注意顺序:深度小的新编号id也会小,所以L=id[top[x]],R=id[x];
        x=f[top[x]];
    }//x,y不停地沿各自所在重链往上跑到重链起始点的父亲,同时更新沿途(当前点到重链顶端)的点权(注:此时的点权指的是w[id[x]],即新编号的点的点权),直到x,y跑到同一条重链上;
    if (dep[x]<dep[y]) swap(x,y);
    update(1,1,n,id[y],id[x],z);//同样注意顺序
}

void updSon(int x,int y) {
    update(1,1,n,id[x],id[x]+siz[x]-1,y);//对于任一节点的子树,因为之前是dfs做预处理,故其所有新编号具有连续性(即若当前子树的根新编号为id[x],那么其子树中最大新编号为id[x]+siz[x]-1,准确的说,这个新编号最大的节点就是这课子树中最后被dfs到的(显然是叶子)节点)
}

int qRange(int x,int y) {
    int ans=0;
    while (top[x]!=top[y]) {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
        x=f[top[x]];
    }
    if (dep[x]<dep[y]) swap(x,y);
    ans=(ans+query(1,1,n,id[y],id[x]))%mod;
    return ans;
}

int qSon(int x) {
    return query(1,1,n,id[x],id[x]+siz[x]-1);
}

signed main(){
    int i,op,x,y,z;
    scanf("%lld%lld%lld%lld",&n,&m,&rt,&mod);
    for (i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (i=1;i<n;i++) {
        scanf("%lld%lld",&x,&y);
        insert(x,y);
        insert(y,x);
    }
    dfs1(rt,0,1);
    dfs2(rt,rt);//两参是两个rt,别写成rt,0了QAQ
    build(1,1,n);
    for (i=1;i<=m;i++) {
        scanf("%lld",&op);
        if (op==1) {
            scanf("%lld%lld%lld",&x,&y,&z);
            updRange(x,y,z%mod);
        }
        else if (op==2) {
            scanf("%lld%lld",&x,&y);
            printf("%lld\n",qRange(x,y));
        }
        else if (op==3) {
            scanf("%lld%lld",&x,&y);
            updSon(x,y%mod);
        }
        else {
            scanf("%lld",&x);
            printf("%lld\n",qSon(x));
        }
    }
    return 0;
}

很显然存在不少第一篇题解的痕迹 (Orz写题解的dalao),毕竟我就是看着它学会的

PS:为了谨慎,全开了long long,其实没必要。

又PS:单行注释过长带来的阅读不便,请见谅(打字不易),可以复制到自己电脑上看。

供大家参考!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值