标签:
这题网上题解比决战好像要多23333
先丢链接跪四位大爷
http://blog.csdn.net/iamzky/article/details/43494481
虽然理解之后大家都讲得很清晰
不过窝看了很久都木有看懂Orz
于是此篇题解尽量平易近人一些吧
窝突然想到一定会有人怀疑我做这种无脑数据结构题的意义……
于是:我的人生,一片无悔~
~~~~~吐槽时间~~~~~
觉得杜教写法不太自然,然后写了个自然的,但是因为要维护子树信息……果断被教做人了
我猜我得调到十点了。一语成谶!
最后甚至都把负数读入优化加上了……还是WA……
我猜我只能明天再说了。再次一语成谶!
要来数据发现木有out,感受到root满满的恶意(我有std我自豪)
妈呀,第一个点就错这么多?我昨天每个梯度都拍了100~1000组啊……
论rand(i-1), i 与 i, rand(i-1)的区别
于是果断花式捉急,感觉简直不会再爱tiao了
调了调之后A了第一个数据,然后测了10个点,都AC了
于是交一发
妈呀,TLE?时间还很诡异?
思考ing……
窝猜是编译超时?我开O2编译需要3s……杜教的0.7s就搞定了……感觉是智商问题
于是乱改了一下,编译时间0.088!好优越!
交上去就CE了233333333333
于是觉得是评测姬傲娇
删了一些东西又交了上去,TLE,妈呀,时间还是诡异!
晚上被UOJ教做人之后无聊点开代码edit-submit
居!然!就!A!了!
白交三次好爽!
你还我的AC ratio!
其实文章里有彩蛋我不会乱说
~~~~~正文时间~~~~~
虽然依赖写的是splay,但是你当然需要会LCT啦~(因为窝写的这两个东西基本没啥差别……)
然后有关toptree是啥之类的问题请参考zky博客……
和上篇文章一样,窝萌考虑LCT
用splay维护链剖分,所以能支持各种链上操作
于是窝萌用一个LCT就可以解决sone1的不带子树部分
于是做完了
当然这是OJ,没有部分分……
窝萌思考一下,如果在LCT上打子树标记,应该怎么下传呢?
吐槽:太sb了!链和节点连出的虚边上传一传不就行了吗!
好吧……
然后怎么传?暴力?
注意到菊花图可以卡到死……
于是窝萌机智一点
用一个二叉树来维护一个节点连出的虚边(注意是一个节点!)
为了复杂度,splay显然
考虑一个事,窝们给每个节点的虚边建了个splay,然后就会有父子关系
为了方便,窝们理所当然应当直接把之前的父子关系改一下(不然你想肿么写程序啊囧……)(如果还是有意见……您以前学数据结构也这样刨根问底求意义吗……)
于是
感受一下吧~
考虑 1-2 1-3 2-4 2-5
3:我的父亲是2,isroot()告诉我他是我爸爸
2:孩子,我不是你父亲,我是你的兄弟啊!父亲在我上面,也是isroot()告诉我的
3:那不是我爷爷吗?
所以窝们不能这样
怎么办呢?考虑一种不会出现这种问题的数据结构
线段树!
吐槽:你不是说过是splay了吗!
窝们可以考虑线段树式splay嘛,每次都建出一个中间节点,只保存段的信息,本身不带信息
对比一下:splay:mid+l+r 线段树:l+r
3:我的父亲是一个诡异的节点,他告诉我我真正的父亲在上面
2:真巧,我也是!
在实现上,窝们给每个节点把ch[2]扩充为ch[4]
就可以用ch[2], ch[3]来访问这个节点连出的每个虚儿子了(注意只是这个节点的虚儿子)
设计好框架之后就剩下两部分了
信息合并和标记的维护
窝们把信息做成三个 chain tree all
chain表示这条链上的信息之和,即原LCT中维护的信息
tree表示这条链连出的虚边的信息之和(不丽洁"这条链"请多想想LCT)
all=chain+tree
chain=s[0]->chain+s[1]->chain
tree=s[0]->tree+s[1]->tree+s[2]->all+s[3]->all
all=chain+tree
这个感受一下就明白了
标记做成两个chaintag treetag
chaintag表示对chain的tag(吐槽:废话),即原LCT中的tag
chaintag会对chain和all造成影响
treetag表示对tree的tag(吐槽:废话)
虽然看上去也是废话……不过这个还是有必要认真说说的
窝们不妨规定treetag对链没有管理权
所以chain上的节点不能利用treetag来更新自己的信息,即treetag只会对tree和all造成影响
一种情况除外,即treetag是从上方虚边(手动标tag也算)传下来的
此时窝们要把这个标记分裂为chain+tree
看上去很容易,只需要传一个flag表示一下是怎么下传的,flag==1时再调用一下chainmodify就行了
不过此处有实现细节,即这样会把all改两次……
一种方法比较显然,手动解决即可
但是窝萌可以更取巧一点
chainmodify里直接all=chain+tree就行了
至此,窝们解决了打标记问题
然后是标记下传
chain->s[0] s[1]
tree->s[0], 0 s[1], 0 s[2], 1 s[3], 1
即可(看不懂, 0的请自行反省为啥不仔细看……)
有了update和pushdown,就可以开始写splay了
显然这个没啥难度
唯一注意的就是因为虚边splay和LCT本身是不一样的
所以需要传flag,并讨论一下isroot()
如果你怀疑过splay虚边是否会导致不满足内节点和叶节点的关系问题的话,我可以告诉你(我也怀疑过),
窝们只需要保证只会转内节点就好了,于是内节点记录一个inner就可以解决问题
有了splay,就可以开始写access了
还是可以这样
for (cuts1(p);p->p!=null;splay(p)) cuts1(p->p), links1(p->p, p);
(注意这里p->p的意义不同,是p->p->p->……中第一个不是内节点的节点)
cuts1(p)就是把p->s[1]加入p的虚边splay并同时考虑是否新建内节点 (add(p, p->s[1]))
links1(p, q)就是把q从它的虚边splay中删除,同时维护内节点性质 (del(q)),再把q变为p的s[1]
(即如果他的父亲是内节点,把兄弟扔到父亲位置就行,可以自行脑补一下(如果你问兄弟是否一定存在……多想想?))
当然,这是一种很形式化的写法,实践中窝们可以这样
如果p->p->s[1]!=null,直接用p->p->s[1]代替p,否则再执行del(p)
(当然,杜教永远都有更屌的写法,每次(p=q)->update,最后一发splay(p0),真正的splay!(其实我们之前对p0算是spaly2333))
有了access,就可以开始写reroot了
注意到链不管怎么翻都和虚边没关系(虚边是无序的)
srO vfleaking Orz
此处盗图(盗图时firefox突然卡住,好了之后吓得我赶紧保存了草稿)(恢复之后找了半天没找到,原来是未载入出来23333)
所以我们可以access(y)一下,或者之前就access(y),然后再update一下
两种方法大同小异
接下来我们再接再厉,解决换父亲
我们需要一个findpre(p),找到链上的前驱节点,即一发p->s[0],然后使劲p->s[1]就行了(中途别忘了pushdown)
先access(prep),然后就可以del(p)了,这样p和prep就断绝关系了(p的子树:我们不想这样啊!)
然后现在p已经是root了,直接用link的后半部分接到另一个节点上就好了
然后就是操作和查询部分
链操作参照LCT,注意最后要reroot(root);
子树操作
本蒟蒻写法:access(prep),p->treemodify(tag, 1), access(p);
杜教写法:access(p), modify(p->value) treemodify(p->s[2], 1) treemodify(p->s[3], 1)
两种写法各有千秋(显然杜教智商压制),两种写法的原因请自行思考~
查询就同理了,也是自行思考~
仔细看到这里就可以开始写了,事实上,本篇文章涉及代码的部分大概经常缺少update……请自行思考并补齐……
代码就不贴咯,具体细节自行YY吧~(其实没啥容易写挂的细节了~)
最后感谢每一个忍受本蒟蒻智商并看到最后的神犇
肯定有吐槽"窝萌" "窝们" "我们"混用的,口亨!
标签:
原文地址:http://www.cnblogs.com/unknown-shadow/p/4295436.html