P4062 [Code+#1] Yazid 的新生舞会

发布时间 2023-10-11 12:29:15作者: MrcFrst_LRY

题外话

我记得第一次看见这道题是几个月前刚开始集训的时候,当时一点思路都没有,但是今天自己做出来了,很喜欢这种感觉!


\(\text{Links}\)

原题传送门

可能更好的阅读体验


题意

求给定序列中有多少个子区间满足众数出现次数严格大于区间长度的一半。


题解

题目要求满足条件的子区间,一个很直接的想法是每次固定左(右)端点,求有多少个右(左)可以与其匹配对答案造成贡献。

那么考虑一个暴力做法:每次固定左端点,然后往后面一直扫,枚举每个右端点,中途记录众数出现次数,然后依次判断即可。时间复杂度为 \(O(n^2)\)

这肯定是过不了的,那么我们再从条件入手,注意到:

  • 严格大于区间长度的一半

于是就说明每个区间对应的这个众数只会有一个!考虑利用一下这个性质。

那么我们可以枚举这个众数。设我们当前枚举的众数为 \(x\),记录一个数组 \(s\)\(s_i\) 表示前 \(i\) 个数中 \(x\) 的出现次数。

沿用上面固定一个端点求另一个合法端点数量的思路,对于一个右端点 \(r\),合法的左端点 \(l\) 显然应该满足:

\[l \le r,s_r-s_{l-1}\gt \frac{r-(l-1)}{2} \]

\(l-1\) 看着有点烦(,把 \(-1\) 去掉,变成:

\[l\lt r ,s_r-s_l\gt \frac{r-l}{2} \]

不等式两边同时 \(\times 2\)

\[l\lt r,2s_r-2s_l\gt r-l \]

移项得:

\[l\lt r,2s_r-r\gt 2s_l-l \]

\(s'_i=2s_i-i\),则:

\[l\lt r,s'_r\gt s'_l \]

经典问题,树状数组维护即可。但时间复杂度为 \(O(n^2\log n)\),甚至不如 \(O(n^2)\) 暴力(悲。

那么我们考虑整体观察一下序列 \(s'\),说不定能发现什么(。

显然地,如果满足 \(a_i=x\) 的若干个 \(i\) 的的位置都确定了,那么整个 \(s'\) 序列就可以确定了,所以我们要想办法只用这些 \(i\) 的位置来计算答案,使得每次的时间复杂度都只与 \(m\) 相关,其中 \(m\) 为这些 \(i\) 的数量。再因为 \(\sum m=n\),于是可以大幅降低总时间复杂度。

\(d\)\(s'\) 的差分序列,那么如果 \(a_i=x\),则有 \(d_i=1\),否则 \(d_i=-1\),我们把前者中的 \(i\) 视为一个“断点”,那么整个 \(s'\) 序列就是若干个公差为 \(-1\)等差数列“首尾衔接”拼在一起。

因为公差为 \(-1\),即单调递减,所以每一个等差数列内部是不会产生任何贡献的,那么我们考虑把每个等差数列视作整体,看它前面的等差数列可以产生多少贡献。

沿用上面树状数组的做法,开一个 \(t\)\(t_i\) 表示 \(s'\) 值为 \(i\) 的位置有多少个,因为每次要查询小于某个值的数量,所以把它记成前缀和的形式,记 \(v\) 为它的前缀和序列。那么对于每个右端点 \(r\),它的答案显然是 \(v_{s'_r-1}\)。我们把每一段等差数列的贡献写下来就是:

\[\sum_{i=fir}^{lst}v_{i-1} \]

然后它又可以写成两个前缀和相减的形式,也就成了 \(t\) 序列的二阶前缀和,那么我们就需要在这个桶上面实现:区间加(插入一个等差数列),维护二阶前缀和。

呵,果然又变成大力 ds 了是吧

线段树和树状数组都可以维护,在我看来各有优势吧,线段树写此题代码的时候比较容易理解,好写一点,但这题树状数组的常数吊打线段树。

这里给出线段树的实现方式: