UperFicial
2021-06-17 20:37:38
这题搞得我吐了
不过第一次写模板题题解还是很开心的呀
感觉这道题题解质量都不高啊……
这里也会讲清楚此题题解并没有提及的一些重要问题。
看懂本篇题解,你需要了解
题目连接:
给定一个长度为
支持区间翻转操作,即翻转区间
输出
但如果翻转了呢?就把中序遍历给翻过来,也就是
考虑我们到底肝了个什么事。
发现,其实就是把这棵树里,每一个节点的两个左右孩子调换后,得到的新树再进行中序遍历,所以上面的这棵树就变成了下面这个样子:
于是,我们一个初步的思路就是:分裂出对应的树后,从根开始,每一层的每一个节点都将两个儿子调换。
但是考虑我们这样的复杂度是多少,我们要进行分裂出来的树的节点的个数次调换,那么复杂度就是
考虑优化,引入线段树那里的懒标记的思想。
还以这棵树为例,前提这棵树是分裂好的了:
现在想对这棵树进行翻转,我们可以先不翻转,而是记上要翻转的名义,那么在哪记?当然是这棵树的根节点也就是
现在问题又来了,我们光知道哪个树需要翻转,但是我们并没有真正地更改儿子,输出的时候怎么办。
这也是懒标记的精髓,用它的时候再更新。当我们要进行分裂、合并时,对于目前的节点
而它的两个儿子调换之后,不光它们要调换,它们的后代也都要调换,所以它们也会承接父亲的懒标记,注意,这里的承接是指的取反,也跟上面讲述的同理,是为了节省翻多次的时间,这里不再赘述。
故到此,我们就实现了标记下传:
inline void push_down(int u)
{
tag[u]=false;
Swap(ch[u][0],ch[u][1]);
tag[ch[u][0]]^=1;
tag[ch[u][1]]^=1;
return;
}
之后还有一个问题:在分裂或合并时什么时候下传标记。
这个问题我想了半天,因为我太 没人发现这个可以点吗
所以,只有我们在分裂之前下传,才能传到真儿子中。当然,合并也是同理的。
这样一来,又有一个问题,我们在进行 push_down
时,会调换两个儿子,那这样怎么能保证
其实,这道题,它跟权值是没有关系的,我们只关心每个子树内节点的顺序如何,我们维护的其实就是翻转后的中序遍历(个人理解)。
所以说我们回到本文开头笔者未解答的问题,怎么分裂出那棵需要翻转的子树?
由于我很
那咋办,分裂不了啥都玩完了了,洗洗睡吧。
既然我们不能按权值分,我们可以按照子树的大小来分!也就是
这样分裂,简单来说,就是分出权值的效果,但又不需要权值。
然后假设我们有如下的一棵树。
圈内代表节点编号,红字代表节点的值。
它显然不满足
那么目前,此序列即为
有一个很显然的性质:对于目前序列一个连续的区间,在这棵树中所对应的节点也是相连的。
那么也很显然,我们只要分裂两次就能分裂出我们先要的区间。
那么分裂就显而易见了,对于翻转
之后就是输出了,当然就是按照中序遍历输出,但还有一个问题,有些节点可能标记没下传下去,遍历的时候也要下传。
呼,讲完了,此生最长的一片题解了
时间复杂度:
空间复杂度:
求赞