一些区间无重和问题

区间无重和问题:

问题描述:

对于一个有序数组 \(a[1…n]\) ,希望\(O(\lg n)\)地查询任意区间 \([l, r]\) 中元素之和,且重复元素只加一次。

解决方法:

对于每个点上的元素,我们额外维护一个前向指针 \(prev\),指向它左方最近一个与之具有相同值的元素。

有了前向指针,对于任一区间\([l, r]\),显然只需遍历一遍,将所有满足 \(prev < l\) 的元素累加,即可得到答案。

接下来思考如何优化这个过程,观察答案的式子:

\[\sum_{prev_i < l} {a_i} \]

我们发现,如果将结点按照 \(prev\) 关键字排序,那么答案就是一个前缀和,因此我们可以在排序后预处理出前缀和,然后二分查找得到答案。

还有区间查询的问题未解决,自然想到线段树:对树上每一个结点作预处理,将其对应区间内的元素进行排序并记录前缀和;对区间\([L, R]\)查询时,只需对线段树上所有子区间二分查找出 \(prev < L\) 的前缀和,然后合并答案即可。时间复杂度\(O(n \lg^2n)\),空间复杂度\(O(n\lg n)\)

缺点:不支持在线增删


重新考虑这个问题,我们要在线地查询

\[\sum_{prev_i < L} \sum_{L \leq i \leq R} a_i \]

直接一个树状数组套线段树就可以解决了,写起来甚至比上一个方法简单)

有一些题目,层层剥开后,就发现就是一个无重和/积问题。
另外,将 \(a_i\) 设置为 \(1\),答案就是区间中去重元素个数。

例题

例1:codeforces 1422 F
刚才说的第一种方法就是从这学的。。
问题描述:给一个正整数序列,每个元素不超过2*1e5,强制在线查询给定区间的LCM。
分析:在素因数分解后,若干个数的LCM即为各个素因子幂次取max,然而,素数太多了,不能这么干,真的不能吗?

注意到 \(n\) 至多只有一个大于 \(\sqrt n\) 的素因子,其余素因子都是不超过 \(\sqrt n\) 的,所以我们先对 450 以下的素因子建立线段树维护区间最大幂次,将这些小的素因子提出后,每一位剩下的只能是 1 或者 素数。问题就转化为对一个素数序列动态查询区间LCM了,而素数们的LCM,不就是去重积吗?

例2:codeforces 1436 E
分析:求一个有序数组的所有子段(一段连续的元素)的Mex的Mex,问题看着很吓人,不过我们首先注意到 \(1 \leq a_i \leq n\),这说明所有子段的Mex一定位于\([1, n + 1]\)中,所以我们可以令 \(x\)\(1\) 遍历到 \(n + 1\),逐步检验是否存在一个子段的Mex为 \(x\)

任何一个Mex为 \(x\) 的子段一定不包含 \(x\),而在不含 \(x\) 的条件下,子段当然越大越有可能满足条件,考虑 \(x\) 会将序列分为若干不含 \(x\) 的极大子段,只需对这些极大子段逐一验证即可,在 \(x\) 遍历的过程中,总共会出现 \(O(n)\) 个极大子段,如何快速地处理每个子段呢?

对不含 \(x\) 的子段进行验证,其实就是判断它是否含有 \(1\)\(x - 1\) 的所有值,我们希望它等价于 “区间元素个数为 \(x - 1\)(去重) ”,为此,只需要在每次遍历完 \(x\) 后,再把所有值为 \(x\) 的元素放入树套树。这样就能 \(O(\lg n)\) 地处理每个极大子段了。

posted @ 2020-11-03 22:14  古明地绿  阅读(89)  评论(0编辑  收藏  举报