[BZOJ3786]星系探索(欧拉序+非旋treap)
题面、
物理学家小C的研究正遇到某个瓶颈。
他正在研究的是一个星系,这个星系中有n个星球,其中有一个主星球(方便起见我们默认其为1号星球),其余的所有星球均有且仅有一个依赖星球。主星球没有依赖星球。我们定义依赖关系如下:若星球a的依赖星球是b,则有星球a依赖星球b.此外,依赖关系具有传递性,即若星球a依赖星球b,星球b依赖星球c,则有星球a依赖星球c.对于这个神秘的星系中,小C初步探究了它的性质,发现星球之间的依赖关系是无环的。并且从星球a出发只能直接到达它的依赖星球b.每个星球i都有一个能量系数wi.小C想进行若干次实验,第i次实验,他将从飞船上向星球di发射一个初始能量为0的能量收集器,能量收集器会从星球di开始前往主星球,并收集沿途每个星球的部分能量,收集能量的多少等于这个星球的能量系数。但是星系的构成并不是一成不变的,某些时刻,星系可能由于某些复杂的原因发生变化。
有些时刻,某个星球能量激发,将使得所有依赖于它的星球以及他自己的能量系数均增加一个定值。还有可能在某些时刻,某个星球的依赖星球会发生变化,但变化后依然满足依赖关系是无环的。
现在小C已经测定了时刻0时每个星球的能量系数,以及每个星球(除了主星球之外)的依赖星球。接下来的m个时刻,每个时刻都会发生一些事件。其中小C可能会进行若干次实验,对于他的每一次实验,请你告诉他这一次实验能量收集器的最终能量是多少。
分析
给出一棵根为1的树,要支持:子树加,换父亲,查询节点到根的路径的点权和。由于树是动态的不能树剖,又需要维护路径信息和子树信息,不妨考虑欧拉序。设进栈序为(l_x),出栈序为(r_x).进栈时把值设为(a_x),出栈时设为(-a_x),那么根据欧拉序的性质有:
- (x)到根路径查询:直接查询([1,l_x])的和即可
- (x)子树加(v):区间([l_x,r_x])加(v),注意由于区间的点符号可能为正或负,要记录所有符号((+1,-1))的和(t),那么综合增加的值就是(vcdot t)
- 将(x)父亲换为(fa):相当于把区间([l_x,r_x])接到新的父亲的入栈序(l_{fa})后面
显然可以用非旋Treap实现,这个算法也被称为欧拉环游树(Euler Tour Tree,ETT).复杂度(O(nlog n))
需要注意换父亲操作导致欧拉序中节点顺序改变,非旋Treapsplit
的时候不能直接split(l[x])
,要在树上先求(l_x)现在排在序列的位置(即到根路径上左子树大小+1的和),然后再按这个排名split
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define maxn 500000
using namespace std;
typedef long long ll;
int n,m;
struct edge {
int from;
int to;
int next;
} E[maxn*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v) {
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].next=head[u];
head[u]=esz;
}
int tim;
int lb[maxn+5],rb[maxn+5],a[maxn+5];
struct fhq_treap {
#define lson(x) (tree[x].ls)
#define rson(x) (tree[x].rs)
#define fa(x) (tree[x].fa)
struct node {
int ls;
int rs;
int fa;
int dat;
int sz;
int tval;
ll tsum;//差分标记和
ll tag;
ll val;//加上差分标记的值
ll sum;
} tree[maxn+5];
int ptr;
int root;
int New(int val,int type) {
ptr++;
tree[ptr].sz=1;
tree[ptr].dat=rand();
tree[ptr].tsum=tree[ptr].tval=type;
tree[ptr].sum=tree[ptr].val=val*type;
return ptr;
}
void push_up(int x) {
tree[x].sz=tree[lson(x)].sz+1+tree[rson(x)].sz;
tree[x].tsum=tree[x].tval+tree[lson(x)].tsum+tree[rson(x)].tsum;
tree[x].sum=tree[x].val+tree[lson(x)].sum+tree[rson(x)].sum;
if(lson(x)) fa(lson(x))=x;
if(rson(x)) fa(rson(x))=x;
}
void add_tag(int x,ll tag) {
tree[x].val+=tree[x].tval*tag;//因为子树内点可能符号不一样,要乘上标记和
tree[x].sum+=tree[x].tsum*tag;
tree[x].tag+=tag;
}
void push_down(int x) {
if(tree[x].tag) {
add_tag(lson(x),tree[x].tag);
add_tag(rson(x),tree[x].tag);
tree[x].tag=0;
}
}
int merge(int x,int y) {
push_down(x);
push_down(y);
if(!x||!y) return x+y;
if(tree[x].dat<tree[y].dat) {
lson(y)=merge(x,lson(y));
push_up(y);
return y;
} else {
rson(x)=merge(rson(x),y);
push_up(x);
return x;
}
}
void split(int now,int k,int &x,int &y) {
if(now==0) {
x=y=0;
return;
}
push_down(now);
if(k<=tree[lson(now)].sz) {
y=now;
split(lson(now),k,x,lson(y));
} else {
x=now;
split(rson(now),k-tree[lson(now)].sz-1,rson(x),y);
}
push_up(now);
}
int get_rank(int x) { //因为换父亲会改变欧拉序对应节点的位置,要查真实排名
int ans=tree[lson(x)].sz+1;
while(fa(x)) {
if(rson(fa(x))==x) ans+=tree[lson(fa(x))].sz+1;
x=fa(x);
}
return ans;
}
void add(int id,int val) {
int x,y,z;
split(root,get_rank(rb[id]),y,z);
fa(y)=fa(z)=0;
split(y,get_rank(lb[id])-1,x,y);//分出id子树对应的区间
fa(x)=fa(y)=0;
add_tag(y,val);
root=merge(merge(x,y),z);
fa(root)=0;
}
ll query(int id) {
int x,y;
split(root,get_rank(lb[id]),x,y);
ll ans=tree[x].sum;
root=merge(x,y);
return ans;
}
void change_fa(int id,int fa_id) {
int x,y,z;
split(root,get_rank(rb[id]),y,z);
fa(y)=fa(z)=0;
split(y,get_rank(lb[id])-1,x,y);//提取出原来的子树
fa(x)=fa(y)=0;
root=merge(x,z);
fa(root)=0;
split(root,get_rank(lb[fa_id]),x,z);//把子树插到新的父亲后面
root=merge(merge(x,y),z);
fa(root)=0;
}
void build(int x,int f) {
lb[x]=New(a[x],1);
root=merge(root,lb[x]);
for(int i=head[x]; i; i=E[i].next) {
int y=E[i].to;
if(y!=f) build(y,x);
}
rb[x]=New(a[x],-1);
root=merge(root,rb[x]);
}
} T;
int main() {
int u,v;
char op[10];
scanf("%d",&n);
for(int i=2; i<=n; i++) {
scanf("%d",&u);
add_edge(u,i);
add_edge(i,u);
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
T.build(1,0);
scanf("%d",&m);
for(int i=1; i<=m; i++) {
scanf("%s",op);
if(op[0]=='Q') {
scanf("%d",&u);
printf("%lld
",T.query(u));
} else if(op[0]=='C') {
scanf("%d %d",&u,&v);
T.change_fa(u,v);
} else {
scanf("%d %d",&u,&v);
T.add(u,v);
}
}
}