填坑,填坑,填坑……
开篇镇人品……下文的比喻仅供娱乐……
为了迎接JSZX校内互测,我临时填坑学了LCT……
怎么说呢……我也是懵懵懂懂地看了N篇博客,对着标程敲上一发代码,然后才慢慢理解。这里推荐Virtual Judge的一个LCT题集,挺良心的:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=25242#overview
这个题集我写了4题(另有一道用的链剖),这个博客里都可以找到代码。
相信其他资料已经说了很多LCT的定义什么的,我假定你已经看过这些,只是对具体操作有些模糊,我只说一些自己的心得。
开始啦
我们可以这么想,首先给你一颗树(或者森林),然后慢慢给你加边,删边什么的。(这个大家庭开始有一些事件,有人要加入(认亲),有人被抛弃(分家)……233)
在加边的时候,我们按每个点在树中的深度作为关键字,弄好多棵Splay(Splay不熟悉的话还是建议先补一下),Spaly可以看成一个联盟,然后在Splay上调来调去……(也就是说,这个家庭每次做出重大调整的时候,一个人被调整的时候无论如何都要带上他的儿女一起走。你爸跟你伯伯分家的时候总不会把你也塞给伯伯吧)
一开始,每棵Splay都包含一个点,大概像这样:
深色的边表示关系比较好,此时它们是一个联盟。
我们在操作的时候,通常是要把要操作的两个点分到两棵Splay(联盟)里,然后把这两个点旋到根,再对这两个首脑进行操作就方便多了,通常情况下,作为一颗Splay的根节点,它的父亲存的是整棵Splay(映射在树中实际是一条链)的父亲(树中这条链离根最近的那个点的父亲,如果这个点已经是根,那么之间为一个特定指针,这个指针往往看你自己的代码风格,也就是爱怎么写就怎么写),删边的时候,这两棵Splay中某一棵的究极祖父(根的父亲存的指针)肯定是另一棵的根(操作合法必须这样),那么之间把这个究极祖父变成自己的空指针就好了,然后在真实的树中这个点就跟它父亲分离(好可怜),变成一棵独立的树啦。当它想家的时候,要回家了,就会带上它的整棵Splay(在此之前先让它成为联盟的首脑),然后把一个联盟并入另一个联盟就好咯。
可以看出,Splay的合并操作要求某个根有一个空儿子,那么我们可以这么做:因为它以深度为关键字,我们在把一个点选举为首脑之前,先把比它深度大的点踢出去(好残忍……),再让它变成根它的右儿子就什么都没有了。这就是所谓的Access操作,给个代码就好了,非常好懂的。
inline void acc(int u){ int x=0; while(u){ splay(u); rt[ch[u][1]]=1;//rt表示一个点是不是联盟的首脑 ch[u][1]=x; rt[ch[u][1]]=0; u=fa[x=u]; } }
然后像我刚才说的那样,link和cut操作也很好想了。
inline void re(int x){ acc(x);splay(x);rev[x]^=1; } inline void link(int x,int y){ re(x);acc(y); ch[y][1]=x;fa[x]=y; } inline void cut(int x,int y){ re(x);acc(y);splay(y); ch[y][0]=fa[ch[y][0]]=0; }
针对各种不同的修改操作,往往需要切换各种姿势打标记(让某个人记得告诉它儿子们某件事),其实也就是splay和acc的不同组合而已。
写得好像还是挫……