【HAOI2015】BZOJ4034 树上操作题解(dfs序+线段树)

题目:BZOJ4034.
题目大意:给定一棵 n n n个节点的树,让你支持3个操作:
1.把某个节点 x x x的点权增加 a a a.
2.把某个节点 x x x为根的子树中所有点的点权都增加 a a a.
3.询问某个节点 x x x到根的路径中所有点的点权和.
设操作数为 m m m,则 1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

这道题显然是道树剖板子题,但是我们强制用dfs序做呢?

我们把dfs序时一个节点入栈出栈各操作一次,现在我们可以让入栈为正,出栈为负,得到一个 1 1 1 − 1 -1 1组成的dfs序.

为什么可以这样做?是因为这样维护 [ 1 , i ] [1,i] [1,i]的总权值就是以 i i i节点到根节点这条链上节点的总权值.

也就是说,一条从 x x x到根节点的链的所有节点,就是这棵树dfs的时候,刚好扫到了 x x x,这是被扫到了却未被回溯过的节点.

用dfs的思路来看这是显然的.

那么怎么维护这棵树呢?

我们可以考虑用dfs序搞下来之后,建树时,若这个元素是入栈顺序,则把flag记为 1 1 1,否则记为 − 1 -1 1,然后合并.

所以AC代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define LL long long
const int N=100000;
int n,m;
LL a[N+1]={0};
struct side{
  int y,next;
}e[2*N+1];
int lin[N+1]={0},top=0;
void ins(int X,int Y){
  e[++top].y=Y;
  e[top].next=lin[X];
  lin[X]=top;
}
bool use[N+1]={0};
int dfn[N+1]={0},low[N+1]={0},tt=0;
LL b[2*N+1]={0};
void dfs(int k){
  use[k]=1;
  dfn[k]=++tt;b[tt]=1LL;
  for (int i=lin[k];i;i=e[i].next)
    if (!use[e[i].y]) dfs(e[i].y);
  low[k]=++tt;b[tt]=-1LL;
}
struct tree{
  int l,r;
  LL sum,flag,tag;
}tr[N*10];
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  tr[k].sum=tr[k].tag=0LL;
  if (L==R){
    tr[k].flag=b[L];
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1);
  build(mid+1,R,k<<1|1);
  tr[k].flag=tr[k<<1].flag+tr[k<<1|1].flag;
}
void pushdown(int k){
  int ls=k<<1,rs=ls|1;
  tr[ls].tag+=tr[k].tag;
  tr[rs].tag+=tr[k].tag;
  tr[ls].sum+=tr[ls].flag*tr[k].tag;
  tr[rs].sum+=tr[rs].flag*tr[k].tag;
  tr[k].tag=0LL;
}
void add(int L,int R,LL num,int k=1){
  if (tr[k].l==L&&tr[k].r==R){
    tr[k].sum+=tr[k].flag*num;
    tr[k].tag+=num;
    return;
  }
  pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) add(L,R,num,k<<1);
  else if (L>mid) add(L,R,num,k<<1|1);
    else add(L,mid,num,k<<1),add(mid+1,R,num,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL query(int L,int R,int k=1){
  if (tr[k].l==L&&tr[k].r==R) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  pushdown(k);
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else return query(L,mid,k<<1)+query(mid+1,R,k<<1|1);
}
inline void into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
  int x,y;
  for (int i=1;i<n;i++){
    scanf("%d%d",&x,&y);
    ins(x,y);ins(y,x);
  }
}
inline void work(){
  dfs(1);
  build(1,2*n);
  for (int i=1;i<=n;i++)
    add(dfn[i],dfn[i],a[i]),add(low[i],low[i],a[i]);
}
inline void outo(){
  int x,opt;
  LL y;
  for (int i=1;i<=m;i++){
    scanf("%d",&opt);
    switch (opt){
      case 1:scanf("%d%lld",&x,&y);
             add(dfn[x],dfn[x],y);add(low[x],low[x],y);
             break;
      case 2:scanf("%d%lld",&x,&y);
             add(dfn[x],low[x],y);
             break;
      case 3:scanf("%d",&x);
             printf("%lld\n",query(1,dfn[x]));
             break;
    }
  }
}
int main(){
  into();
  work();
  outo();
  return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值