COGS-2551 新型武器

题目大意:

有一棵树, 现要求支持以下两种操作: 

1, 把u号点的权值改为v;

2, 求以u号点为根的子树中, 深度为v的点的权值和.

保证给边时先给父亲再给儿子, 1号点为根.

首先看到求权值和, 先把线段树准备好.

这一题明显不可能是树链剖分线段树了, 我们必须想到一个新的顺序, 来建起这个线段树.

再看询问, 以u为根的子树内部深度为v的点的权值和.

也就是说, 求的是u的子树内部, 在整棵树里面深度为deep[ u ]+v的点的权值和.

而在u的子树内部, 那么记一下dfs每个点进入和离开的时间戳dfn和ed, 就是求dfs序在dfn[ u ]到ed[ u ]之间的, 深度为deep[ u ]+v的点的权值和.

那么剩下的东西就没有太大的难度了. 

我们把每个点按照深度为第一关键字, dfs序为第二关键字排序, 按照这个顺序建线段树.

那么我们首先快速地得到指定深度是在哪个区间, 然后二分找到dfs序的左右边界, 再查询就可以了, 总复杂度就是O( Qlogn ).

快速得到指定深度的区间, 只需要在确定每个点的顺序的时候顺便记一下某个深度第一次出现是在第几个位置depnum, 那么深度x的区间就是depnum[ x ]到depnum[ x+1 ]-1; 如果x是最深的, 右端点是n.

最后特判一下不存在这个深度, 或者求的深度为0( 就是子树的根 )的情况.

最后还有几句题外话, 天使姐姐是我的, 我的线段树就是快.

//made by Crazy01
#include<bits/stdc++.h>
#define inf (1<<30)
#define ll long long
#define db double
#define c233 cout<<"233"<<endl
#define mem(s) memset(s,0,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define ls (cd<<1)
#define rs (cd<<1|1)
const int N=300050;
using namespace std;
 
struct lll{
  int id,dfn,deep;
  bool operator <(const lll &a)const{
    return deep==a.deep?dfn<a.dfn:deep<a.deep;
  }
}p[N];
int nxt[N],to[N],head[N],pw[N],ed[N],whi[N],depnum[N];//whi记录点i在排序之后的序列中排第几
int n,q,maxe,cnt_dfn,aim,det,al,ar;
ll sum[N<<2];
 
inline int gi(){
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+ch-48,ch=getchar();
  return x*res;
}
 
void build(int f,int t){
  nxt[++maxe]=head[f]; to[maxe]=t; head[f]=maxe;
}
 
void init(){
  n=gi();
  for(int i=1;i<=n;i++)pw[i]=gi();
  for(int i=1;i<n;i++){
    int a=gi(),b=gi();
    build(a,b);
  }
  q=gi();
}
 
void dfs(int x,int dep){
  p[x].dfn=++cnt_dfn; p[x].deep=dep;
  p[x].id=x;
  for(int i=head[x];i;i=nxt[i]){
    int u=to[i];
    dfs(u,dep+1);
  }
  ed[x]=cnt_dfn;
}
 
//-------------------------------------------------
 
void buildx(int cd,int l,int r){
  if(l==r){sum[cd]=pw[p[l].id]; return;}
  int mid=(l+r)>>1;
  buildx(ls,l,mid); buildx(rs,mid+1,r);
  sum[cd]=sum[ls]+sum[rs];
}
 
ll query(int cd,int l,int r,int L,int R){
  if(l==L&&r==R)return sum[cd];
  int mid=(l+r)>>1;
  if(R<=mid)return query(ls,l,mid,L,R);
  else if(L>mid)return query(rs,mid+1,r,L,R);
  else return query(ls,l,mid,L,mid)+query(rs,mid+1,r,mid+1,R);
}
 
void change(int cd,int l,int r){
  if(l==r){sum[cd]=det; return;}
  int mid=(l+r)>>1;
  if(aim<=mid)change(ls,l,mid);
  else change(rs,mid+1,r);
  sum[cd]=sum[ls]+sum[rs];
}
 
//------------------------线段树部分-----------------
 
int lyb(int l,int r,int x){
  if(r<=0)r=n;
  while(l<=r){
    int mid=(l+r)>>1;
    if(p[mid].dfn>x)r=mid-1;
    else l=mid+1;
  }
  return r;
}//二分找区间
 
void getans(int po,int dep){
  if(dep==0){printf("%d
",pw[po]); return;}
  int aimdep=dep+p[whi[po]].deep;
  if(!depnum[aimdep]){printf("0
"); return;}
  al=lyb(depnum[aimdep],depnum[aimdep+1]-1,p[whi[po]].dfn)+1;
  ar=lyb(depnum[aimdep],depnum[aimdep+1]-1,ed[po]);
  printf("%lld
",query(1,1,n,al,ar));
}
 
void pre(){
  sort(p+1,p+1+n);
  buildx(1,1,n);
  for(int i=1;i<=n;i++){
    whi[p[i].id]=i;
    if(!depnum[p[i].deep])depnum[p[i].deep]=i;
  }
}
 
void work(){
  for(int i=1;i<=q;i++){
    int opt=gi(),a=gi(),b=gi();
    if(opt==1){
      aim=whi[a]; det=b; pw[a]=det;
      change(1,1,n);
    }
    else getans(a,b);
  }
}
 
int main(){
  //freopen("newweapon.in","r",stdin);
  //freopen("newweapon.out","w",stdout);
  init();
  dfs(1,0);
  pre();
  work();
  return 0;
}

其实还有另外一种做法, 对每一个深度建线段树, 动态开点, 具体请看隔壁大保健的博客.

原文地址:https://www.cnblogs.com/Crazy01/p/7724651.html