标签:一点 为什么 减法 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