Codeforces Round #716 (Div. 2) D. Cut and Stick

显然:
对于一个区间我们只需要负责处理最多次出现的那个元素
(我们把剩下的元素称为其他元素)
而最优策略是拿两个最多元素和一个其他元素组合成一个序列

故某个区间的答案是
m a x ( 最 多 元 素 个 数 − 其 他 元 素 , 1 ) max(最多元素个数-其他元素,1) max(,1)

所以对于单个区间,我们只需要求出其中出现次数最多的元素个数。
这个是好做的。
因为每次加上一个数或者删去一个数,区间出现次数最多元素的元素个数只会增加或者减少一,所以我们只需要用一个 c n t [ i ] cnt[i] cnt[i]记录 i i i出现的次数, r n k [ i ] rnk[i] rnk[i]记录出现次数为 i i i次的元素个数。
每次维护 c n t [ i ] cnt[i] cnt[i] r n k [ i ] rnk[i] rnk[i]即可。(可以知道这个的复杂度是很低的,因为上文"出现次数最多元素的元素个数只会增加或者减少一")

同时因为询问是连续区间,所以想到莫队,复杂度可以控制在 O ( n n ) O(n\sqrt n) O(nn )以内。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn=3e5+50;
    int n,q;
    int a[maxn],ans[maxn],rnk[maxn],cnt[maxn];
    int nowl=1,nowr=0,nowans=0;
    struct qu
    {
        int l,r,num;
    }query[maxn];
     
    bool cmp(qu a,qu b)
    {
        int t=sqrt(n);
        if(a.r/t!=b.r/t)return a.r/t<b.r/t;
        return a.l<b.l;
    }
     
    void add(int x)
    {
        if(cnt[a[x]]>0)rnk[cnt[a[x]]]--;
        cnt[a[x]]++;
        rnk[cnt[a[x]]]++;
        nowans=max(nowans,cnt[a[x]]);
    }
    void del(int x)
    {
        rnk[cnt[a[x]]]--;
        cnt[a[x]]--;
        rnk[cnt[a[x]]]++;
        while(rnk[nowans]==0)nowans--;
    }
     
    int main()
    {
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d",&query[i].l,&query[i].r);
            query[i].num=i;
        }
        sort(query+1,query+1+q,cmp);
        for(int i=1;i<=q;i++)
        {
            while(nowr<query[i].r)
            {
                nowr++;
                add(nowr);
            }
            while(nowl>query[i].l)
            {
                nowl--;
                add(nowl);
            }
            while(nowr>query[i].r)
            {
                del(nowr);
                nowr--;
            }
            while(nowl<query[i].l)
            {
                del(nowl);
                nowl++;
            }
    //        cout<<nowans<<' '<<"aa"<<endl;
            if(nowans<=(query[i].r-query[i].l+1+1)/2)ans[query[i].num]=1;
            else ans[query[i].num]=max(1,nowans-(query[i].r-query[i].l+1-nowans));
        }
        for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
       	return 0;
    }

但就本题而言我们的做法可以有很多种,其中有两种觉得非常有借鉴意义。
1、如果有一个数在区间内出现了超过 ( n + 1 ) / 2 (n+1)/2 (n+1)/2次,那么它一定在某个子区间内出现超过 ( n + 1 ) / 2 (n+1)/2 (n+1)/2次(否则矛盾),所以我们只需要用线段树处理出每个区间超过 ( n + 1 ) / 2 (n+1)/2 (n+1)/2次的数,然后分别 c h e c k check check即可。
2、如果有一个数在区间内出现了超过 ( n + 1 ) / 2 (n+1)/2 (n+1)/2次,那么我们随机一个位置,随机到这个数的概率大于 1 / 2 1/2 1/2,所以我们随机 n n n次, c h e c k check check每次取到的值,找到这个数的概率是非常大的。
注:check就简单二分下标~

同时顺便也学到了,对于出现次数大于 ( n + 1 ) / 2 (n+1)/2 (n+1)/2的众数我们有摩尔投票算法可以 O ( n ) O(n) O(n)求出…
b y   t h e   w a y by\,the\,way bytheway我们还可以复习一下中位数的一些冷知识,对于一个区间我们可以先二分一个数,然后统计大于这个数和小于这个数的数的个数,来 c h e c k check check,复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值