A - 大佬集中营

这篇博客介绍了一种利用可持久化权值线段树解决区间众数问题的算法。文章首先阐述了问题背景,即给定一个序列,求解区间内数的集合划分,使得每个集合中出现次数最多的元素不超过集合大小的一半。接着,作者揭示了答案与区间众数之间的关系,并提出当众数超过一半时的解决方案。随后,通过建立可持久化的权值线段树来实现快速查询,并在没有修改操作的静态场景下优化空间。文章最后提供了代码实现,展示了如何查找超过区间长度一半的众数并进行查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A - 大佬集中营

题目大意

给定一个序列,每次询问某个区间中的数,能分成最少多少个集合,每个集合中出现次数最多的次数不能超过集合大小的一半(上取整)。而且每个集合的大小尽可能大。

题解

不难发现,一个区间里的所以数,一定都是会被分配完的,只有因为一个数的集合也是满足题意的。

接下来,最关键的一步,就是发现答案与区间众数的关系:

  • 如果该区间的众数,都没有超过一半,那很显然,只需要分成一组
  • 当其众数超过一半了,则不妨假设众数为k,答案就是 2 × k − ( r − l + 1 ) 2 \times k - (r - l + 1) 2×k(rl+1)

有了这个结论,就非常简单了,因为并没有修改操作,是静态的,那样每一个位置建一棵权值线段树,维护每个数出现次数的前缀和。

显然,这样的空间是无法接受的,于是可持久化一下,变成主席树就可以了。

至于如何寻找众数呢?

其实主席树也做不到,但是要找超过区间长度一半的众数还是可以找到的,因为它是唯一的。

每次在左右儿子中,寻找一个出现次数超过一半的儿子往下走,如果两个儿子都没有超过一半,则说明没有符合要求的众数,即答案为1。

时间复杂度

主席树每次询问的时间复杂的是 O ( log ⁡ n ) O(\log n) O(logn),整体的时间复杂度就是 O ( n × log ⁡ n ) O(n\times\log n ) O(n×logn),只不过常数有点大。

Tag

可持久化

权值线段树

code

//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define G getchar
#define ls(x) tr[x].l
#define rs(x) tr[x].r
#define s(x) tr[x].s
using namespace std;

int read()
{
    char ch;
    for(ch = G();(ch < '0' || ch > '9') && ch != '-';ch = G());
    int n = 0 , w;
    if (ch == '-')
    {
        w = -1;
        ch = G();
    } else w = 1;
    for(;'0' <= ch && ch <= '9';ch = G())n = (n<<1)+(n<<3)+ch-48;
    return n * w;
}

const int N = 300005;
int n , m , t , l , r , tot , opv;
int root[N];

struct node
{
    int l , r , s;
}tr[N*30];

inline void updata (int x)
{
    s(x) = s(ls(x)) + s(rs(x));
}

void ins (int x , int xx , int l , int r)
{
    if (l == r)
    {
        s(xx) = s(x) + 1;
        return;
    }
    int m = (l + r) >> 1;
    if (opv <= m)
    {
        rs(xx) = rs(x);
        ls(xx) = ++ tot;
        ins(ls(x) , ls(xx) , l , m);
    }
    else
    {
        rs(xx) = ++ tot;
        ls(xx) = ls(x);
        ins(rs(x) , rs(xx) , m + 1 , r);
    }
    updata(xx);
}

int find (int x , int xx , int l , int r)
{
    if (s(xx) - s(x) <= opv) return 0;
    if (l == r) return s(xx) - s(x);
    int m = (l + r) >> 1;
    if (s(ls(xx)) - s(ls(x)) > s(rs(xx)) - s(rs(x)))
        return find(ls(x) , ls(xx) , l ,m);
            else return find(rs(x) , rs(xx) , m + 1 , r);
}

int main()
{
    freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    n = read(); m = read();
    for (int i = 1; i <= n; ++i)
    {
        opv = read();
        root[i] = ++ tot;
        ins(root[i-1] , root[i] , 1 , n);
    }
    for (int i = 0; i < m; ++i)
    {
        l = read(); r = read();
        opv = ((r - l) >> 1) + 1;
        t = find(root[l -1] , root[r] , 1 , n);
        if (t == 0) printf("1\n");
            else printf("%d\n", 2 * t - (r - l + 1));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值