众数问题

Published: by Creative Commons Licence

  • Tags:

区间众数

题目1:给定一个序列a1,,an,要求回答q个请求,第i个请求给定三个参数li,ri,ki,要求找出区间al,,ar中出现次数超过rili+1ki的最小的数,如果不存在则输出1。其中n,q1051k5

这个问题有很多做法。

第一种就是用莫队。莫队内部维护一个统计表,统计每个数在区间中出现的次数。且由于每次每个数出现次数变化最多为1,因此对于每个分块,分配一个链表数组,第i个元素记录该分块中所有出现次数为i的元素组成的链表。很显然这样我们不管某个数出现次数是加1还是减1,都可以O(1)完成,且最重要的是我们可以实时统计每个分块中的最大值。在回答请求的时候,我们可以利用分块中的最大值先找满足条件的编号最小的分块,之后在分块中暴力查找。因此总的时间复杂度为O((n+q)n)

莫队的做法优点是与k无关,缺点是常数比较大,且必须离线请求。

第二种做法就是发现,对于某个回答,我们可以不断从区间中选择k个不同的数,之后删除。最后留下来的数不超过k1个,我们可以证明结果一定在这留下来的数中。这种区间消除的方式,我们可以用线段树实现,这样每次线段树的查询和修改的时间复杂度都是O(k2log2n)。之后如果验证结果呢,这个我们可以通过持久化技术加差分或者直接上倍增完成,校验一个数的时间复杂度为O(log2n),总的时间复杂度为O((q+n)k2log2n)

第二种做法的优点是支持在线(我们可以为每个数都开一个稀疏线段树),缺点就是k不能过大。

还有一种非常简单的做法就是直接上持久化线段树+差分的技术。线段树的每个结点记录区间中每个数出现的次数。之后对于每个请求,我们暴力扫描线段树,加上一旦发现区间中总数少于阈值就直接退出的剪枝。可以证明一次请求,线段树每一层最多有k个顶点被访问(因为阈值决定了每一层最多有k个条件满足条件)。总的时间复杂度为O((n+kq)log2n)

第三种做法可以充分利用k很小的特性。但是k比较大的时候还是用莫队比较好。

一道题目

问题2:给定一个序列a1,,an,以及q个请求,每个请求指定一个区间l,r,要求找到序列al,,ar中的众数(出现次数最多的数),如果有多个,就输出其中最小的。这里n,q105,要求在线处理请求

一道题目

先离散化数据。

这道题目的核心是发现,要计算AB的区间众数,其仅可能为A的区间众数,或者是B中的某个元素。

上面这个观察允许我们用分块解决这个问题。按照大小n进行分块。

之后考虑一次请求,可以知道答案要么出现在区间两端的块中,要么就是区间中间部分的众数。

我们可以维护两个数组cp。其中c(i,j)表示前i块中j的出现次数。这样我们可以利用差分的技巧来快速计算某个数字在连续块中总的出现次数。而p(i,j)表示第i块到第j块中的众数。数组c可以直接暴力计算每一块的信息最后统计前缀和,而p可以暴力枚举两端块,之后通过p(i,j1)快速转移。预处理的时间复杂度和空间复杂度均为O(n1.5)

每次请求我们也能以O(n)求解,枚举每个可能的众数即可(总共只有O(n)种可能)。

总的时间复杂度为((n+q)n)

问题3:给定一个序列a1,,an,找到一段最长的子数组(从头部和尾部删除若干个元素得到),满足子数组中众数非唯一,如果不存在这样的子数组,返回0。其中1n105

昨天比赛的一道题目

称满足众数非唯一的数组称为好数组。假设1为整个数组的一个众数,我们可以直接证明任意最长的好数组一定包含1,否则我们可以扩大数组两端得到以1为众数的另外一个更长的好数组。

接下来,我们来考虑如何求这样一个好数组。我们可以暴力枚举另外一个众数x。找到x出现次数大于等于1出现次数的子数组,我们可以发现这样的数组一定可以扩展为好数组。当然这样做时间复杂度会变成O(n2),一种方式就是使用分块。我们只枚举出现次数超过n的元素。而对于出现次数不超过n的元素,我们知道这样的区间中1的出现次数也不会超过n。于是乎我们可以分别对1出现次数为1,2,,n暴力遍历数组a,每次遍历都使用双指针,整体的时间复杂度为O(nn)

存在众数的连续子序列计数

一个连续子序列称为是好的,当且仅当其中有一个数出现超过半数以上,这个数称为众数。

现在给定一个长度为n的序列a1,,an,要求统计有多少连续子序列是好的。

提供一道题目

一种简单的思路是使用分块。先考虑所有出现次数不超过n的数,它们能形成的连续子序列长度不会超过2n。因此我们枚举所有长度不超过2n的连续子序列,如果它里面存在众数,且总数全局出现次数不超过n

而对于出现次数超过n的数x,我们可以构建一个序列b,其中如果ai=x,则bi=1,否则bi=1。问题就变成了有多少子数组和是正数。这个问题咋看之下需要维护一个树状数组来解决,但是实际上由于前缀和只会+1和-1,因此可以通过维护一个普通计数数组就可以了,于是这个新问题的时间复杂度为O(n)。而x最多取n个数,因此总的时间复杂度为O(nn)

下面讲另外一种思路,可以将时间复杂度优化到O(n)

具体就是我们不分块处理,对于每个不同的数x,我们都构建b序列,并处理。很显然这样做的时间复杂度为O(nD)Da序列中的不同数数目。

假设xa中出现k次,我们可以尝试将时间复杂度优化到O(k)。具体思路是我们可以把b中连续的-1合并为到一起。假设有一块连续的-1左边界为l,右边界为r,且以l1为右边界的最大子数组和为L,以r+1为左边界的最大子数组和为R,如果L+R(rl+1)0,这意味着不可能有好数组包含这一整块-1。因此我们可以把这一段-1删除到只剩下R+L个,这不会影响我们的结果。可以发现这样做,会导致从左向右扫描的时候每个1最多会激活一个-1,同理从右往左扫描的时候每个1最多会激活一个-1。因此这样处理完后最多有2k1,我们需要处理的b序列长度仅为3k,用同样的算法,我们可以O(3k)处理。

总的时间复杂度为O(n)