先上结论:在刚刚进入 insert/erase 时重构,或者递归完后重构是正确的。
我发现网上很多博客的写法,都是在递归结束之前重构。然而如果有一条链上的点的子树都需要重构,这会浪费很多时间。因此有的人就会将重构的工作放在向下递归之前做,然而这种写法有个细节:
//version 1
remake(p);//重构函数
if(val[p]==v) return cnt[p]++,maintain(p);
//version 2
if(val[p]==v) return cnt[p]++,maintain(p);
remake(p);
这里 version 1 是正确的,version 2 在判完边界条件后重构,会出现重构完成后刚好处在边界条件的情况(期望插入/删除的 v 旋转上来作为 p 的值),但是这时程序就完全无力判断,然后递归找一个不存在的东西,然后就寄了。
检查方法:在删除函数前加上 assert(p),如果正常的话删除不会遍历到一个真的空节点。
正确写法:
void insert(T v,int &p){
if(!p) return p=newnode(v),void();
remake(p);
if(val[p]==v) return cnt[p]++,maintain(p);
insert(v,ch[p][v>val[p]]),maintain(p);
}
void insert(T v,int &p){
if(!p) return p=newnode(v),void();
if(val[p]==v) return cnt[p]++,maintain(p);
insert(v,ch[p][v>val[p]]),remake(p),maintain(p);
}
//erase 同理
hack version 2 的数据:对着程序中 α 的值解一个不等式,求出当插入 x 个点后会在根上重构的最小的 x,然后依次插入 1,2,3,⋯,x,突然删除 x/2,就会出现删不掉的情况。
希望能对 WA 76 pts 的同学有帮助。至此。