VII.[SHOI2014]三叉神经树
LCT相较于树剖,最大的优势就是可以把一条链上的东西全整到一个splay里面,不像树剖在多个重链进行合并时会有很大麻烦。更好的是,LCT复杂度是\(O(n\log n)\)的,树剖不仅码量大,思路复杂,复杂度还是恶心的\(O(n\log^2n)\)。
这题就是典型的树剖被LCT全方面完爆。
首先,这道题要考阅读理解。翻译一下,就是给你一棵树,树上的每个节点要么有三个儿子,要么是叶子。每个叶子初始时有一个或\(0\)或\(1\)的权值。每个非叶子节点的权值由它三个儿子的权值决定,\(0\)和\(1\)在它儿子中占比更多的那一个即是它的权值。现在我们每次修改一个叶子的权值,要你输出修改后根的输出。
我们可以发现,在修改一个叶子时,只有叶子到根的这条路径上点会受到影响。并且,影响必定是连续的一段,一旦有一个点没有被修改,那这次修改就到头了。
设一个节点三个儿子的权值和为\(sum\),则如果\(sum>1\),这个节点输出为\(1\);否则,为\(0\)。
则每次修改,假设是\(0\)改\(1\),只有从叶子到根的一段连续的\(sum\)为\(1\)的点才会被修改。\(sum\)为\(0\)的,多一个\(1\)的儿子只会让\(sum\)变成\(1\),输出还是\(0\);\(sum\)为\(2\)或\(3\)的,更不用说了,\(sum\)增加它的权值还是\(1\)。只有\(sum\)为\(1\)的会受到影响。
则我们只需要维护路径上深度最大的非\(1\)节点。这样,比它深的所有节点都有修改。
对于我们修改一个叶子节点\(x\)时:
令它的父亲为\(y\)。则我们\(access(y)\),打通从\(y\)到\(ROOT\)的路径。
等等,为什么是\(y\)?不应该是\(x\)吗?
因为\(x\)的输入不由\(sum_x\)决定,你要是这么\(access(x)\)会让\(x\)变成普通节点,由它的\(sum_x\)决定,而\(sum_x\)始终为\(0\)。
并且,如果这样的话,我们就不能直接找到\(x\)的父亲,因为同一splay中的父亲不一定是真父亲。
我们\(access(y)\)后,\(splay(y)\),并找到维护的非\(1\)节点的位置,设为\(id_1\)。
如果\(id_1\)不存在,说明从叶子到根这一整条路径上所有的点都得改,直接在\(y\)上打\(tag\)。
否则,设\(z=id_1\)。则\(splay(z)\)后,\(z\)的右儿子即是我们修改的目标。我们直接在\(z\)的右儿子上打\(tag\),同时更新\(z\)的\(sum\)。
现在我们来讨论一下怎么维护\(id\)。
先上函数:
void pushup(int x){
if(t[rson].id[1])t[x].id[1]=t[rson].id[1];
else if(t[x].sum!=1)t[x].id[1]=x;
else t[x].id[1]=t[lson].id[1];
if(t[rson].id[2])t[x].id[2]=t[rson].id[2];
else if(t[x].sum!=2)t[x].id[2]=x;
else t[x].id[2]=t[lson].id[2];
}
因为深度越大越靠右,我们转移时优先选择右儿子,其次自己,最次左儿子。
至于这个\(id_2\),则是在叶子节点的输入由\(1\)转\(0\)时用的,因为由\(1\)转\(0\)只会影响到\(sum=2\)的节点。
代码:
#include<bits/stdc++.h>
using namespace std;
#define lson t[x].ch[0]
#define rson t[x].ch[1]
int n,m,in[1501000],ans;
struct LCT{
int ch[2],id[3],fa,sum,tag,val;
}t[1501000];
int identify(int x){
if(t[t[x].fa].ch[0]==x)return 0;
if(t[t[x].fa].ch[1]==x)return 1;
return -1;
}
void pushup(int x){
if(t[rson].id[1])t[x].id[1]=t[rson].id[1];
else if(t[x].sum!=1)t[x].id[1]=x;
else t[x].id[1]=t[lson].id[1];
if(t[rson].id[2])t[x].id[2]=t[rson].id[2];
else if(t[x].sum!=2)t[x].id[2]=x;
else t[x].id[2]=t[lson].id[2];
}
void modi(int x,int tag){
t[x].sum+=tag,t[x].val=(t[x].sum>1);
swap(t[x].id[1],t[x].id[2]);
t[x].tag+=tag;
}
void pushdown(int x){
if(!t[x].tag)return;
if(lson)modi(lson,t[x].tag);
if(rson)modi(rson,t[x].tag);
t[x].tag=0;
}
void rotate(int x){
int y=t[x].fa;
int z=t[y].fa;
int dirx=identify(x);
int diry=identify(y);
int b=t[x].ch[!dirx];
if(diry!=-1)t[z].ch[diry]=x;t[x].fa=z;
if(b)t[b].fa=y;t[y].ch[dirx]=b;
t[x].ch[!dirx]=y,t[y].fa=x;
pushup(y),pushup(x);
}
void pushall(int x){
if(identify(x)!=-1)pushall(t[x].fa);
pushdown(x);
}
void splay(int x){
pushall(x);
while(identify(x)!=-1){
int fa=t[x].fa;
if(identify(fa)==-1)rotate(x);
else if(identify(fa)==identify(x))rotate(fa),rotate(x);
else rotate(x),rotate(x);
}
pushup(x);
}
void access(int x){
for(int y=0;x;x=t[y=x].fa)splay(x),rson=y,pushup(x);
}
queue<int>q;
int main(){
scanf("%d",&n);
for(int i=1,t1,t2,t3;i<=n;i++)scanf("%d%d%d",&t1,&t2,&t3),t[t1].fa=t[t2].fa=t[t3].fa=i,in[i]=3;
for(int i=n+1;i<=3*n+1;i++)scanf("%d",&t[i].val),q.push(i);
while(!q.empty()){
int x=q.front();q.pop();
if(!t[x].fa)continue;
if(x<=n)pushup(x);
t[t[x].fa].sum+=t[x].val,in[t[x].fa]--;
if(!in[t[x].fa])t[t[x].fa].val=(t[t[x].fa].sum>1),q.push(t[x].fa);
}
ans=t[1].val;
scanf("%d",&m);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d",&x),y=t[x].fa;
access(y),splay(y);
int dir=t[x].val?2:1,val=t[x].val?-1:1;
if(t[y].id[dir]){
z=t[y].id[dir];
splay(z);
modi(t[z].ch[1],val),pushup(t[z].ch[1]);
t[z].sum+=val,t[z].val=(t[z].sum>1),pushup(z);
}else modi(y,val),ans^=1,pushup(y);
t[x].val^=1;
printf("%d\n",ans);
}
return 0;
}