【bzoj3730】震波【动态树分治】

震波

Description

在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i]。
不幸的是,这片土地常常发生地震,并且随着时代的发展,城市的价值也往往会发生变动。
接下来你需要在线处理M次操作:
0 x k 表示发生了一次地震,震中城市为x,影响范围为k,所有与x距离不超过k的城市都将受到影响,该次地震造成的经济损失为所有受影响城市的价值和。
1 x y 表示第x个城市的价值变成了y。
为了体现程序的在线性,操作中的x、y、k都需要异或你程序上一次的输出来解密,如果之前没有输出,则默认上一次的输出为0。

Input

第一行包含两个正整数N和M。
第二行包含N个正整数,第i个数表示value[i]。
接下来N-1行,每行包含两个正整数u、v,表示u和v之间有一条无向边。
接下来M行,每行包含三个数,表示M次操作。

Output

包含若干行,对于每个询问输出一行一个正整数表示答案。

这道题是动态树分治的模板题。

简要的思路:

先建出分治树,每次修改和查询都在这个节点在分治树到根的路径上的所有祖先上面进行操作。

具体实现:

对每一个节点开2棵线段树,保存分治树上这个节点子树的信息。

第一棵线段树:以子树中所有点到这个点的距离为下标,点权为权值,维护区间的和。
第二棵线段树:以子树中所有点到这个点的分治树上的父亲的距离为下标,点权为权值,维护区间的和。
修改:在到根的路径上的所有线段树中删除这个点原来的点权,加入新的点权。
查询:在分治树上从查询点往根爬的时候,统计答案。设查询距离为d1,当前节点的父亲到查询点的距离为d2。答案加上当前节点的父亲维护的子树内第一棵线段树下标为0~d1-d2的点权总和,再减去当前节点维护的子树内第二棵线段树下标为0~d1-d2点权总和,因为从下面爬上来时,当前点的贡献已经计算过了,需要减掉,不然会重复。

于是就搞定啦!
代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200005;
int n,m,op,u,v,cnt,idx,tot,ans,Log2[N*2],a[N],head[N],to[N*2],nxt[N*2],dfn[N],dep[N],siz[N],pos[N*2],f[N*2][25];
int rt,mi,size,fa[N],root[N][2],sumv[N*50],ch[N*50][2],dd[N];
bool vis[N];
void adde(int u,int v){
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
}
void dfs(int pre,int u){
    dfn[u]=++idx;
    pos[idx]=u;
    siz[u]=1;
    int v;
    for(int i=head[u];i;i=nxt[i]){
        v=to[i];
        if(v!=pre){
            dep[v]=dep[u]+1;
            dfs(u,v);
            siz[u]+=siz[v];
            pos[++idx]=u;
        }
    }
}
void st(){
    for(int i=1;i<=idx;i++){
        f[i][0]=pos[i];
    }
    for(int j=1;j<=20;j++){
        for(int i=1;i+(1<<j)-1<=idx;i++){
            if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]){
                f[i][j]=f[i][j-1];
            }else{
                f[i][j]=f[i+(1<<(j-1))][j-1];
            }
        }
    }
}
int lca(int u,int v){
    if(dfn[u]>dfn[v]){
        swap(u,v);
    }
    int k=Log2[dfn[v]-dfn[u]];
    if(dep[f[dfn[u]][k]]<dep[f[dfn[v]-(1<<k)+1][k]]){
        return f[dfn[u]][k];
    }else{
        return f[dfn[v]-(1<<k)+1][k];
    }
}
int dis(int u,int v){
    return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd(int &o,int l,int r,int k,int v){
    if(k<l||k>r){
        return;
    }
    if(!o){
        o=++tot;
    }
    sumv[o]+=v;
    if(l==r){
        return;
    }
    int mid=(l+r)/2;
    if(k<=mid){
        upd(ch[o][0],l,mid,k,v);
    }else{
        upd(ch[o][1],mid+1,r,k,v);
    }
}
int qry(int o,int l,int r,int L,int R){
    if(!o||L>r){
        return 0;
    }
    if(L==l&&R==r){
        return sumv[o];
    }
    int mid=(l+r)/2;
    if(R<=mid){
        return qry(ch[o][0],l,mid,L,R);
    }else if(L>mid){
        return qry(ch[o][1],mid+1,r,L,R);
    }else{
        return qry(ch[o][0],l,mid,L,mid)+qry(ch[o][1],mid+1,r,mid+1,R);
    }
}
void dfsroot(int pre,int u){
    siz[u]=1;
    int v,mx=0;
    for(int i=head[u];i;i=nxt[i]){
        v=to[i];
        if(!vis[v]&&v!=pre){
            dfsroot(u,v);
            siz[u]+=siz[v];
            mx=max(mx,siz[v]);
        }
    }
    mx=max(mx,size-siz[u]);
    if(mx<mi){
        rt=u;
        mi=mx;
    }
}
void init(int rt,int md,int pre,int u){
    upd(root[rt][md],0,n,dd[u],a[u]);
    int v;
    for(int i=head[u];i;i=nxt[i]){
        v=to[i];
        if(!vis[v]&&v!=pre){
            dd[v]=dd[u]+1;
            init(rt,md,u,v);
        }
    }
}
void dfstree(int u){
    vis[u]=true;
    int v;
    dd[u]=0;
    init(u,0,0,u);
    for(int i=head[u];i;i=nxt[i]){
        v=to[i];
        if(!vis[v]){
            mi=size=siz[v];
            dd[v]=1;
            dfsroot(u,v);
            init(rt,1,u,v);
            fa[rt]=u;
            dfstree(rt);
        }
    }
}
int query(int u,int d){
    int ans=qry(root[u][0],0,n,0,d),tmp;
    for(int i=u;fa[i];i=fa[i]){
        tmp=dis(fa[i],u);
        ans+=qry(root[fa[i]][0],0,n,0,d-tmp)-qry(root[i][1],0,n,0,d-tmp);
    }
    return ans;
}
void update(int u,int k){
    int tmp;
    upd(root[u][0],0,n,0,k-a[u]);
    for(int i=u;fa[i];i=fa[i]){
        tmp=dis(fa[i],u);
        upd(root[fa[i]][0],0,n,tmp,k-a[u]);
        upd(root[i][1],0,n,tmp,k-a[u]);
    }
    a[u]=k;
}
int main(){
    for(int i=2;i<=200000;i++){
        Log2[i]=Log2[i/2]+1;
    }
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        adde(u,v);
        adde(v,u);
    }
    dfs(0,1);
    st();
    size=mi=siz[1];
    dfsroot(0,1);
    dfstree(rt);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&op,&u,&v);
        u^=ans;
        v^=ans;
        if(op==0){
            printf("%d\n",ans=query(u,v));
        }else{
            update(u,v);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值