区间众数

一、块状数组

块状数组就是将数组划分若干块(一般为sqrt(n)使复杂度最低),通过预处理i~j块的答案(复杂度可以保证在n*sqrt(n)内),

求答案时就根据预处理的答案和暴力枚举首尾多余个数得出最后答案,首尾多余的个数是不超过2*sqrt(n),这样总的复杂度

为((n+q)sqrt(n)),q为查询的次数。

二、区间众数

1.题目描述

有n个数x,查询q次,求【L,R】区间的众数个数(其中n,q<=20000,0<=L<=R<n)。

大部分一般做到的众数问题是可以暴力区间求答案的,但是这里数据过大无法暴力,我们需要使用块状数组解决这类问题(复杂度n*sqrt(n))。

为了使问题更一般性假设每个数0<=x<n。(实际上x都可以转换成这个范围,稍后会解决此问题)


首先预处理i~j块的众数个数,如果求出i~j-1的众数个数了,现在要求i~j的众数个数,是不是就很简单了,众数个数要么是i~j-1的众数个数,要么就是第j块中数字出现的次数,那么就可以枚举第j块的数字,如果我们预处理出前i块数字j出现的次数(定义为sum[i][j]),那么第j块出现的数字a[p](其中p处于第j块中)在i~j块出现的次数为sum[j][ a[p] ]-sum[i-1][ a[p] ]。并且sum数字的预处理很简单。

预处理完成后,就是查询问题,查询其实和预处理的原理是一样的,假设L属于第x块,R属于第y块,那么此区间的众数个数要么是x+1~y-1块的众数个数,要么是L~第x块结束中数字出现的次数,要么就是第y块开始~R中数字出现的次数,由于x+1~y-1块的众数个数已经处理,那么只需要O(1)即可知道,另外两块就可以直接暴力统计个数就行(这里需要注意只需要初始化这两块中出现的数字,这样初始化复杂度只有sqrt(n),初值应为sum[y-1][p]-sum[x][p],p为块中出现的数字),另外注意处理L和R处于同一块的情况,这样区间众数问题就解决了。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50005, N = 305;
int sum[N][maxn], a[maxn], mul[N][N], cc[N][N];
int tmp[maxn];
//sum[i][j]统计前i块数字j出现的次数
//mul[i][j]计算众数个数最多且数字最少的数
//cc[i][j]计算众数最多个数
void solve ( )
{
    int n, Q;
    //freopen ( "in0.in", "r", stdin );
    //freopen ( "in1.out", "w", stdout );
    scanf ( "%d%d", &n, &Q );
    for ( int i = 0; i < n; i ++ )
        scanf ( "%d", &a[i] );
    int blo = ( int )sqrt ( n )+1;
    int cnt = ( n+blo-1 )/blo;
    for ( int i = 1; i <= blo; i ++ )
    {
        for ( int j = 0; j < n; j ++ )
            sum[i][j] = sum[i-1][j];
        for ( int j = ( i-1 )*cnt; j < n && j < i*cnt; j ++ )
            sum[i][ a[j] ] ++;
    }
    for ( int i = 1; i <= blo; i ++ )
    {
        for ( int j = i; j <= blo; j ++ )
        {
            cc[i][j] = cc[i][j-1];	//注意初始化
            mul[i][j] = mul[i][j-1];
            for ( int k = ( j-1 )*cnt; k < n && k < j*cnt; k ++ )
            {
                int x = sum[j][ a[k] ]-sum[i-1][ a[k] ];
                if ( x > cc[i][j] || x == cc[i][j] && mul[i][j] > a[k] )
                {
                    cc[i][j] = x;
                    mul[i][j] = a[k];
                }
            }
        }
    }
    int L, R, ans, anspos;
    //ans次数最多,anspos次数最多且数字最小
    while ( Q -- )
    {
        scanf ( "%d%d", &L, &R );
        int x = L/cnt+1, y = R/cnt+1;
        ans = cc[x+1][y-1], anspos = mul[x+1][y-1];
        for ( int i = L; i <= R && i < x*cnt; i ++ )
            tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
        for ( int i = max ( L, ( y-1 )*cnt ); i <= R; i ++ )
            tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
        for ( int i = L; i < x*cnt && i <= R; i ++ )
        {
            tmp[ a[i] ] ++;
            if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos )
            {
                ans = tmp[ a[i] ];
                anspos = a[i];
            }
        }
        if ( x != y )	//同块处理
        {
            for ( int i = ( y-1 )*cnt; i <= R; i ++ )
            {
                tmp[ a[i] ] ++;
                if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos )
                {
                    ans = tmp[ a[i] ];
                    anspos = a[i];
                }
            }
        }
        printf ( "%d %d\n", anspos, ans );
    }
}
int main ( )
{
    solve ( );
    return 0;
}

当x值不在区间[0,n)时,我们可以通过离散将其映射到[0,n)中。

离散化代码:

memcpy ( b, a, sizeof ( a ) );
sort ( a, a+n );
for ( int i = 0; i < n; i ++ )
	b[i] = lower_bound ( a, a+n, b[i] )-a;

三、带修改的区间众数

由于我们很好的解决了不带修改的区间众数,那么那些预处理都是一样的,只不过我们需要做的是在修改时将sum和cc的值更新就行了,修改一个数时会影响blo^2个区间(blo为块数),如果blo=sqrt(n),那么复杂度为n,这样是不可接受的,我们将blo=n^(1/3),那么复杂度就为n^(2/3),如果我们同时预处理第i~j块数字x出现的次数(记bb[i][j][x]),第i~j块出现次数为x的个数(记tim[i][j][x]),当修改a[L]=R时(L属于第x块),将影响所有包含x块的区间块,由于bb[i][j][x]和tim[i][j][x]的修改很简单,那么cc[i][j]的值是怎样呢?由于最大次数的变化范围在[-1,1]之间(因为以前的数的次数减1,现在的数加1,众数个数可能不止一个,所以众数变化[-1,1]之内),所以可以直接判断tim的个数就可以求出cc的值,sum的值可以用一个循环更新,总的复杂度为O((n+q)*n^(2/3))。

在这里bb[i][j]和tim[i][j]的处理和前面cc[i][j]的处理是一样的,已经求出了i~j-1的值,只需要将bb[i][j-1],tim[i][j-1],cc[i][j-1]的值复制(注意开始写的时候很容易出现初始值初始错误的问题)。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 20005, N = 28;
int sum[N][maxn], a[maxn], cc[N][N];
int tmp[maxn], tim[N][N][maxn], bb[N][N][maxn];
void solve ( )
{
    int n, Q;
    //freopen ( "in0.in", "r", stdin );
    //freopen ( "in1.out", "w", stdout );
    scanf ( "%d%d", &n, &Q );
    for ( int i = 0; i < n; i ++ )
        scanf ( "%d", &a[i] );
    int blo = ( int )pow ( n, 1.0/3 )+1;
    int cnt = ( n+blo-1 )/blo;
    for ( int i = 1; i <= blo; i ++ )
    {
        for ( int j = 0; j < n; j ++ )
            sum[i][j] = sum[i-1][j];
        for ( int j = ( i-1 )*cnt; j < n && j < i*cnt; j ++ )
            sum[i][ a[j] ] ++;
    }
    for ( int i = 1; i <= blo; i ++ )
    {
        for ( int j = i; j <= blo; j ++ )
        {
            cc[i][j] = cc[i][j-1];
            for ( int k = 0; k < n; k ++ )  //初始化
            {
                bb[i][j][k] = bb[i][j-1][k];
                tim[i][j][k] = tim[i][j-1][k];
            }
            for ( int k = ( j-1 )*cnt; k < n && k < j*cnt; k ++ )
            {
                int p = bb[i][j][ a[k] ] ++;
                tim[i][j][p] --;
                tim[i][j][p+1] ++;
                int x = sum[j][ a[k] ]-sum[i-1][ a[k] ];
                if ( x > cc[i][j] )
                    cc[i][j] = x;
            }
        }
    }
    int op, L, R, ans;
    while ( Q -- )
    {
        scanf ( "%d%d%d", &op, &L, &R );
        if ( op )
        {
            int x = L/cnt+1, y = R/cnt+1;
            ans = cc[x+1][y-1];
            for ( int i = L; i <= R && i < x*cnt; i ++ )
                tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
            for ( int i = ( y-1 )*cnt; i <= R; i ++ )
                tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
            for ( int i = L; i < x*cnt && i <= R; i ++ )
            {
                tmp[ a[i] ] ++;
                if ( tmp[ a[i] ] > ans )
                    ans = tmp[ a[i] ];
            }
            if ( x != y )
            {
                for ( int i = ( y-1 )*cnt; i <= R; i ++ )
                {
                    tmp[ a[i] ] ++;
                    if ( tmp[ a[i] ] > ans )
                        ans = tmp[ a[i] ];
                }
            }
            printf ( "%d\n", ans );
        }
        else
        {
            int p = a[L];
            a[L] = R;
            int x = L/cnt+1;
            for ( int i = x; i <= blo; i ++ )
            {
                sum[i][p] --;
                sum[i][R] ++;
            }
            for ( int i = 1; i <= blo; i ++ )
            {
                for ( int j = i; j <= blo; j ++ )
                {
                    if ( i <= x && x <= j ) //包含x块
                    {
                        int t = bb[i][j][p] --;
                        tim[i][j][t] --;    //次数和个数变化
                        tim[i][j][t-1] ++;
                        t = bb[i][j][R] ++;
                        tim[i][j][t] --;
                        tim[i][j][t+1] ++;
                        t = cc[i][j];
                        if ( tim[i][j][t-1] )//判断是否存在个数
                            cc[i][j] = t-1;
                        if ( tim[i][j][t] )
                            cc[i][j] = t;
                        if ( tim[i][j][t+1] )
                            cc[i][j] = t+1;
                    }
                }
            }
        }
    }
}
int main ( )
{
    solve ( );
    return 0;
}


四、修改的区间众数的其他问题
1.数字的x不限定在[0,n)时,同样可以通过离散化处理,但是因为有修改,所以需要将所有修改的数加入后在排序,那么就限定在[0,2n)区间内了。

2.如果还是需要得到次数最多且数字最小的数字时,可以将tim定义为set。


<think>好的,我现在要解决用户的问题:在区间查询中求众数的算法或实现方法。首先,我需要明确用户的需求。用户可能在进行大数据处理或数据分析时,需要快速查询某个区间内的众数,即出现次数最多的元素。这在实际应用中很常见,比如统计某个时间段内的热门商品、高频访问的网页等。 接下来,我需要回忆相关的算法和数据结构。已知众数的定义是出现次数超过半数的元素,但这里的区间众数不一定满足这个条件,所以可能需要不同的方法。根据已有的引用资料,分治法和使用哈希表是常见的方法。不过,这些方法是否适用于区间查询呢? 首先,引用[2]提到分治法处理大数据集,将数据分成子集处理,再合并结果。但分治法通常用于静态数据集,而区间查询可能需要动态处理不同范围的查询,这可能需要预处理数据结构。 引用[1]和[3]提到使用哈希表统计频率,或者排序后取中间元素(适用于存在绝对众数的情况)。但直接对每个查询区间进行哈希统计会导致较高的时间复杂度,尤其是当查询次数多时,效率不高。 引用[4]是关于分治法求众数的具体实现,但同样处理的是整个数据集,而不是区间查询。因此,可能需要寻找适合区间查询的数据结构。 这时,我想到线段树(Segment Tree)可能适用,因为它可以高效处理区间查询问题。线段树每个节点存储区间信息,如最大值、最小值、频率等。但存储众数信息比较复杂,因为众数需要知道每个元素的频率,而不同区间的合并可能影响众数的结果。 另一个思路是使用莫队算法(Mo's Algorithm),这是一种处理离线区间查询的高效方法,通过对查询进行排序和分块,减少重复计算。莫队算法通常用于统计频率类问题,适合求众数。 此外,还有基于分块的方法,将数据分成多个块,预处理每个块的频率信息,以及块之间的众数候选,查询时结合预处理的信息和块内的统计,减少计算量。 需要比较这些方法的优缺点。线段树的预处理时间和空间可能较高,但查询时间快;莫队算法适合离线查询,但需要处理查询顺序;分块方法在预处理和查询之间取得平衡。 然后,考虑具体实现。例如,分块方法:将数组分成√n大小的块,预处理每个块的众数候选和相邻块的信息。查询时,将区间分为左部分、中间完整块、右部分,统计各部分的频率,合并后确定众数。 另外,对于在线查询且需要实时响应的场景,可能需要更高效的数据结构,比如使用持久化线段树(Persistent Segment Tree)来记录每个位置的频率历史,从而快速查询任意区间的频率。 但实现复杂度较高,尤其是对于大规模数据。因此,分块或莫队算法可能更实用,尤其是当查询可以离线处理时。 最后,需要验证这些方法的正确性和效率,并参考已有的文献或资料,确保推荐的算法是可行且有效的。例如,论文或算法导论中提到的区间众数问题解决方案,可能采用上述方法之一。</think>### 区间查询求众数的实现方法 #### 1. **分块法** 将数组分为大小为 $\sqrt{n}$ 的块,预处理每块的频率统计和候选众数。 - **预处理**: - 对每个块,统计元素频率并记录当前块的候选众数。 - 对每两个相邻块,记录合并后的候选众数。 - **查询**: - 区间覆盖完整块时,直接使用预处理结果。 - 对不完整的左右部分,遍历统计频率,并与中间块的候选众数合并比较。 - **时间复杂度**: - 预处理 $O(n \sqrt{n})$,单次查询 $O(\sqrt{n})$。 - 适合中等规模数据,实现简单[^2][^4]。 #### 2. **线段树(优化版)** 每个线段树节点存储频率最高的元素及其出现次数,合并时比较左右子节点的众数和交叉区域的频率。 - **合并策略**: - 左右子节点的众数中频率更高者为候选。 - 还需检查中间重叠区域的元素是否可能成为新众数(需额外记录频率分布)。 - **缺点**: - 维护复杂,空间占用高,适用于元素范围有限的情况[^3]。 #### 3. **莫队算法(离线查询)** - **步骤**: 1. 将查询按块排序,按特定顺序处理以减少重复计算。 2. 维护当前区间的频率哈希表,动态更新众数。 - **时间复杂度**: - 单次查询均摊 $O(\sqrt{n})$,适合大规模离线查询。 #### 4. **哈希表+频率统计(暴力法)** - 直接遍历查询区间,用哈希表统计频率并记录最大值。 - **时间复杂度**: - 单次查询 $O(r-l+1)$,适用于小范围查询[^1][^3]。 --- ### 示例代码(分块法实现) ```python import math from collections import defaultdict class BlockMode: def __init__(self, arr): self.n = len(arr) self.block_size = int(math.sqrt(self.n)) + 1 self.blocks = [] self.freq = [] # 每块的频率表 self.mode_candidates = [] # 每块的候选众数 # 预处理每个块 for i in range(0, self.n, self.block_size): block = arr[i:i+self.block_size] freq = defaultdict(int) current_max = (0, None) for num in block: freq[num] += 1 if freq[num] > current_max[0]: current_max = (freq[num], num) self.freq.append(freq) self.mode_candidates.append(current_max[1]) def query(self, l, r): mode = None max_count = 0 temp_freq = defaultdict(int) # 遍历左不完整块 while l <= r and l % self.block_size != 0: num = arr[l] temp_freq[num] += 1 if temp_freq[num] > max_count or (temp_freq[num] == max_count and num < mode): max_count = temp_freq[num] mode = num l += 1 # 处理完整块 while l + self.block_size <= r: block_idx = l // self.block_size candidate = self.mode_candidates[block_idx] # 合并频率 count_in_block = self.freq[block_idx].get(candidate, 0) total_count = temp_freq[candidate] + count_in_block if total_count > max_count: max_count = total_count mode = candidate l += self.block_size # 遍历右不完整块 while l <= r: num = arr[l] temp_freq[num] += 1 if temp_freq[num] > max_count or (temp_freq[num] == max_count and num < mode): max_count = temp_freq[num] mode = num l += 1 return mode ``` --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值