HDU 3966(树链剖分)

传送门

题意:

给你一个有 n n n个顶点的树,树上的每一个点都有一个点权,现在有 3 3 3种操作:

  • I   u   v   k I~ u~v~k I u v k,代表将结点 u u u到结点 v v v的最近的路径上的所有点的点权增加 k k k
  • D   u   v   k D~ u~v~k D u v k ,代表将结点 u u u到结点 v v v的最近的路径上的所有点的点权减少 k k k
  • Q   u Q~ u Q u,代表求出结点 u u u的点权。

题目分析:

树链剖分的模板题。

倘若只有操作 1 1 1和操作 2 2 2,则我们只需要用树上差分用 O ( n + m ) \mathcal{O}(n+m) O(n+m)的时间复杂度完成操作。

但是,现在这个问题中,因此操作 3 3 3的存在,使得用树上差分去做的话时间复杂度将会不优。

因此我们可以考虑采用树链剖分。

我们将树上的重链剖分出来之后,获取出他们的 d f s dfs dfs序,并用数据结构(线段树/树状数组)去维护区间的和即可。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
struct Node{
    int to,next;
}q[100005];
struct ST{
    int sum,len,lazy;
}tr[maxn<<2];
int head[maxn],cnt=0,tot=0,f[maxn],dis[maxn],son[maxn],size[maxn],top[maxn];
int id[maxn],rk[maxn],v[maxn];
void add_edge(int from,int to){
    q[cnt].to=to;
    q[cnt].next=head[from];
    head[from]=cnt++;
}
void dfs1(int x){
    size[x]=1,dis[x]=dis[f[x]]+1;
    for(int i=head[x];i!=-1;i=q[i].next){
        int to=q[i].to;
        if(to==f[x]) continue;
        f[to]=x;
        dfs1(to);
        size[x]+=size[to];
        if(size[to]>size[son[x]]){
            son[x]=to;
        }
    }
}
void dfs2(int x,int t){
    top[x]=t;
    id[x]=++tot;
    rk[tot]=x;
    if(son[x]) dfs2(son[x],t);
    for(int i=head[x];i!=-1;i=q[i].next){
        int to=q[i].to;
        if(to==son[x]||to==f[x]) continue;
        dfs2(to,to);
    }
}
void push_up(int rt){
    tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
}
void push_down(int rt){
    if(tr[rt].lazy){
        tr[rt<<1].sum+=1ll*tr[rt].lazy*tr[rt<<1].len;
        tr[rt<<1|1].sum+=1ll*tr[rt].lazy*tr[rt<<1|1].len;
        tr[rt<<1].lazy=tr[rt<<1].lazy+tr[rt].lazy;
        tr[rt<<1|1].lazy=tr[rt<<1|1].lazy+tr[rt].lazy;
        tr[rt].lazy=0;
    }
}
void build(int l,int r,int rt){
    tr[rt].lazy=0;
    tr[rt].len=r-l+1;
    if(l==r){
        tr[rt].sum=v[rk[l]];
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    push_up(rt);
}
void update(int L,int R,int l,int r,int rt,int k){
    if(L<=l&&R>=r){
        tr[rt].lazy=tr[rt].lazy+k;
        tr[rt].sum=tr[rt].sum+tr[rt].len*k*1l;
        return ;
    }
    push_down(rt);
    int mid=(l+r)>>1;
    if(L<=mid) update(L,R,l,mid,rt<<1,k);
    if(R>mid) update(L,R,mid+1,r,rt<<1|1,k);
    push_up(rt);
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r){
        return tr[rt].sum;
    }
    push_down(rt);
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid) res+=query(L,R,l,mid,rt<<1);
    if(R>mid) res+=query(L,R,mid+1,r,rt<<1|1);
    return res;
}
int cal(int x,int y){
    int res=0;
    while(top[x]!=top[y]){
        if(dis[top[x]]<dis[top[y]]) swap(x,y);
        res+=query(id[top[x]],id[x],1,tot,1);
        x=f[top[x]];
    }
    if(id[x]>id[y]) swap(x,y);
    res+=query(id[x],id[y],1,tot,1);
    return res;
}
void UPDATE(int x,int y,int c){
    while(top[x]!=top[y]){
        if(dis[top[x]]<dis[top[y]]) swap(x,y);
        update(id[top[x]],id[x],1,tot,1,c);
        x=f[top[x]];
    }
    if(id[x]>id[y]) swap(x,y);
    update(id[x],id[y],1,tot,1,c);
}
int main()
{
    int n,m,t;
    while(~scanf("%d%d%d",&n,&m,&t)){
        memset(head,-1,sizeof(head));
        memset(dis,0,sizeof(dis));
        memset(son,0,sizeof(son));
        tot=0;
        cnt=0;
        for(int i=1;i<=n;i++) scanf("%d",&v[i]);
        for(int i=1;i<n;i++){
            int from,to;
            scanf("%d%d",&from,&to);
            add_edge(from,to);
            add_edge(to,from);
        }
        dfs1(1);
        dfs2(1,1);
        build(1,tot,1);
        while(t--){
            int x,y,z;
            char op[2];
            scanf("%s",op);
            if(op[0]=='I'){
                scanf("%d%d%d",&x,&y,&z);
                UPDATE(x,y,z);
            }
            if(op[0]=='D'){
                scanf("%d%d%d",&x,&y,&z);
                z=-z;
                UPDATE(x,y,z);
            }
            if(op[0]=='Q'){
                scanf("%d",&x);
                int res=query(id[x],id[x],1,tot,1);
                printf("%d\n",res);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值