【UOJ46】玄学

这道题是一个月之前就应该会的了,但是一直没有弄懂,今天回放了一遍,把它弄懂了。

题意简述:

一个操作是把位于$[l..r]$的所有元素$x$变换成$(ax+b)\%m$。

强制在线,每次定义一个新的操作,或是询问只作用变换$l..r$则位于$k$位置的元素应该变成多少。

题解:

这道题很神仙,应用的新的思路。

题解上说是二进制分组,但其实里面只开了线段树,就是这个地方没有弄懂。

我们不妨分析一下这个操作,考虑一下它的性质吧。

先分析有没有交换律,发现显然是没有的。

但是我们惊喜的发现这个操作有结合律,所以或许可以用线段树维护,一个节点的操作可以看成左右儿子叠加形成。

进一步的,我们能否对操作求逆,即如果我们知道了操作$[1..l-1]$、$[1..r]$后的结果,能不能推出$[l..r]$的操作。

发现这样是不行的,假设$ax+b$经过操作$cx+d$之后变成了$ex+f$,当$p$不为质数时,方程$ac ≡ e (mod\ p)$无唯一的解。

我们考虑两种形式的线段树分治。

一种是线段树对位置开,对于一个修改操作,我们确定$log$个区间,节点内部因为已经考虑了位置,就按时间排序,建线段树,查询的时候一条链查下去就可以了。

但是发现这样做很$fake$,因为这个操作是没有交换律的(对于论文上可以说是修改贡献不独立)所以肯定不能这样做。

一种是线段树对操作序列开,也就是题解的做法,但遗憾的是,我依旧没有想出来是怎么做的。

说说我的做法困难在哪里,

刚刚已经说了操作有结合律,所以对于查询的时候,我们可以在线段树上确定$log$个区间,然后依次进行合并。

但是对于我们节点内部,仿佛除了问题规模缩小以外,没有其他的突破,依旧需要按照时间的顺序把所有覆盖了$loc$位置的区间依次合并,这在我看来是一个棘手的二维问题($APIO2019T3$),是不能得到好的解决方法的。

不破不立。

借鉴一般的启发式合并的方法,线段树的每个节点维护这些操作完成后,序列长什么样,也就是一个$vector$,一个元素代表一段区间。

但是这样做$pushup$的复杂度就和左右儿子的区间个数有关了,所以肯定不能像普通的线段树那样,修改一点就对一条链从下到上$pushup$。

因为这样修改是从左到右进行的,所以当还没有到$x$时,线段树上所有覆盖了$x$的区间都是无需维护的,从位置上考虑,我们仅需要对左边的祖先$pushup$就可以了。

$Gloid$爷说这样做是两个$log$的,让我来分析一下。

线段树上有$2n$个节点,平摊到每一个位置就是$O(1)$的,而一次$pushup$复杂度是左右儿子区间的个数之和,一个点最多$pushup$一下,所以修改总的是一个$log$的,查询需要二分,是两个$log$的(但是常数极小)。

神奇啊,被认为是瓶颈的修改竟然没有背这个黑锅。

这个解决的方法可以扩展到一般的二进制分组问题(要求访问历史状态的修改,乃至对一段区间的修改应用),要求是修改具有结合律。

但是大部分修改都不具有结合律,如我们不能快速合并两个凸包,也不能快速合并两个$AC$自动机,可能这个做法的用处是比较窄的,多数二进制分组的题都是把组拆掉重建的。

posted @ 2019-10-06 21:50  HellPix  阅读(246)  评论(0编辑  收藏  举报