标签:一点 为什么 减法 define 前缀和 for chain clu size
挺好的题
看到移动子树和子树加法,首先应该想到用平衡树维护dfs序
到根路径之和?(陷入沉思
我们做一点微小的工作改动,把dfs序换成入栈出栈序列$s$,每个点入栈时记权值为正,出栈时记权值为负
容易看出$\sum\limits_{k=1}^{ind_i} s_k$就是根到$i$路径上的权值和,其中$ind_i$代表$i$入栈时在$s$中的位置
为什么?因为不在路径上的点入栈一次出栈一次,就抵消掉了
那么我们可以用splay来维护这个序列
移动子树:直接提取区间$[ind_i,oud_i]$然后接到新的位置,其中$oud_i$代表$i$出栈时在$s$中的位置
子树加法:同样提取区间$[ind_i,oud_i]$打标记
前缀和:提取区间$[1,ind_i]$直接输出
要维护的标记还挺多:这个点的权值,它是出栈还是入栈,以它为根的子树权值和,以它为根的子树大小,子树增加值
注意我们在记录子树大小时,对入栈点是加法,对出栈点是减法,所以‘子树大小‘实际上记录的是(入栈点大小为$1$,出栈点大小为$-1$)的加权子树大小
注意,这里因为有区间移动,所以提取区间$[l,r]$不是简单的splay($l-1$)然后splay($r+1$),而应该是splay($pre_l$)然后splay($next_r$),这里的$pre_l$是$l$的前驱,$next_r$是$r$的后继
移动$x$的子树到$y$时记得更新$x$和$y$的信息
I?人類~
#include<stdio.h> #define ll long long struct edge{ int to,nex; }e[100010]; int ch[200010][2],fa[200010],ind[100010],oud[100010],h[100010],nv[100010],root,tot,n; ll sum[200010],laz[200010],v[200010],siz[200010],io[200010]; void add(int a,int b){ tot++; e[tot].to=b; e[tot].nex=h[a]; h[a]=tot; } void dfs(int x){ tot++; sum[tot]=nv[x]; v[tot]=nv[x]; io[tot]=1; siz[tot]=1; ind[x]=tot; for(int i=h[x];i;i=e[i].nex)dfs(e[i].to); tot++; sum[tot]=-nv[x]; v[tot]=-nv[x]; io[tot]=-1; siz[tot]=-1; oud[x]=tot; } int build(int l,int r){ int mid=(l+r)>>1,ls=0,rs=0; if(l<mid)ls=build(l,mid-1); if(mid<r)rs=build(mid+1,r); if(ls){ ch[mid][0]=ls; fa[ls]=mid; sum[mid]+=sum[ls]; siz[mid]+=siz[ls]; } if(rs){ ch[mid][1]=rs; fa[rs]=mid; sum[mid]+=sum[rs]; siz[mid]+=siz[rs]; } return mid; } void pushup(int x){ sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+v[x]; siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+io[x]; } void pushdown(int x){ if(laz[x]){ if(ch[x][0]){ sum[ch[x][0]]+=siz[ch[x][0]]*laz[x]; v[ch[x][0]]+=io[ch[x][0]]*laz[x]; laz[ch[x][0]]+=laz[x]; } if(ch[x][1]){ sum[ch[x][1]]+=siz[ch[x][1]]*laz[x]; v[ch[x][1]]+=io[ch[x][1]]*laz[x]; laz[ch[x][1]]+=laz[x]; } laz[x]=0; } } void rot(int x){ int y,z,B; y=fa[x]; z=fa[y]; if(root==y)root=x; int f=(ch[y][0]==x); B=ch[x][f]; fa[x]=z; fa[y]=x; if(B)fa[B]=y; ch[x][f]=y; ch[y][f^1]=B; if(ch[z][0]==y)ch[z][0]=x; if(ch[z][1]==y)ch[z][1]=x; pushup(y); pushup(x); } void chain(int x){ if(x!=root)chain(fa[x]); pushdown(x); } void splay(int x){ int y,z; chain(x); while(x!=root){ y=fa[x]; z=fa[y]; if(y==root) rot(x); else{ if((ch[z][0]==y&&ch[y][0]==x)||(ch[z][1]==y&&ch[y][1]==x)){ rot(y); rot(x); }else{ rot(x); rot(x); } } } } int nexsc(int x){ if(ch[x][1]){ for(x=ch[x][1];ch[x][0];x=ch[x][0]); return x; } while(ch[fa[x]][0]!=x)x=fa[x]; return fa[x]; } int presc(int x){ if(ch[x][0]){ for(x=ch[x][0];ch[x][1];x=ch[x][1]); return x; } while(ch[fa[x]][1]!=x)x=fa[x]; return fa[x]; } ll query(int x){ splay(nexsc(ind[x])); return sum[ch[root][0]]; } void change(int x,int y){ splay(presc(ind[x])); root=ch[root][1]; splay(nexsc(oud[x])); int p=ch[root][0],i; ch[root][0]=0; siz[root]-=siz[p]; sum[root]-=sum[p]; root=fa[root]; siz[root]-=siz[p]; sum[root]-=sum[p]; splay(ind[y]); root=ch[root][1]; for(i=root;ch[i][0];i=ch[i][0]); splay(i); ch[root][0]=p; fa[p]=root; pushup(root); root=fa[root]; pushup(root); } void modify(int x,ll y){ if(x==1){ laz[root]+=y; sum[root]+=siz[root]*y; v[root]+=io[root]*y; return; } splay(presc(ind[x])); root=ch[root][1]; splay(nexsc(oud[x])); laz[ch[root][0]]+=y; sum[ch[root][0]]+=siz[ch[root][0]]*y; v[ch[root][0]]+=io[ch[root][0]]*y; pushup(root); root=fa[root]; pushup(root); } int main(){ int m,i,a,b; char s[5]; scanf("%d",&n); for(i=2;i<=n;i++){ scanf("%d",&a); add(a,i); } for(i=1;i<=n;i++)scanf("%d",nv+i); tot=0; dfs(1); root=build(1,n<<1); scanf("%d",&m); while(m--){ scanf("%s",s); if(s[0]==‘Q‘){ scanf("%d",&a); printf("%lld\n",query(a)); } if(s[0]==‘C‘){ scanf("%d%d",&a,&b); change(a,b); } if(s[0]==‘F‘){ scanf("%d%d",&a,&b); modify(a,(ll)b); } } }
标签:一点 为什么 减法 define 前缀和 for chain clu size
原文地址:http://www.cnblogs.com/jefflyy/p/7532420.html