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

D. Cut and Stick

D. Cut and Stick
time limit per test3 seconds
memory limit per test512 megabytes
inputstandard input
outputstandard output
Baby Ehab has a piece of Cut and Stick with an array a of length n written on it. He plans to grab a pair of scissors and do the following to it:

pick a range (l,r) and cut out every element al, al+1, ..., ar in this range;
stick some of the elements together in the same order they were in the array;
end up with multiple pieces, where every piece contains some of the elements and every element belongs to some piece.
More formally, he partitions the sequence al, al+1, ..., ar into subsequences. He thinks a partitioning is beautiful if for every piece (subsequence) it holds that, if it has length x, then no value occurs strictly more than ⌈x2⌉ times in it.

He didn't pick a range yet, so he's wondering: for q ranges (l,r), what is the minimum number of pieces he needs to partition the elements al, al+1, ..., ar into so that the partitioning is beautiful.

A sequence b is a subsequence of an array a if b can be obtained from a by deleting some (possibly zero) elements. Note that it does not have to be contiguous.

Input
The first line contains two integers n and q (1≤n,q≤3105) — the length of the array a and the number of queries.

The second line contains n integers a1, a2, ..., an (1≤ai≤n) — the elements of the array a.

Each of the next q lines contains two integers l and r (1≤l≤r≤n) — the range of this query.

Output
For each query, print the minimum number of subsequences you need to partition this range into so that the partitioning is beautiful. We can prove such partitioning always exists.

Example
inputCopy
6 2
1 3 2 3 3 2
1 6
2 5
outputCopy
1
2
Note
In the first query, you can just put the whole array in one subsequence, since its length is 6, and no value occurs more than 3 times in it.

In the second query, the elements of the query range are [3,2,3,3]. You can't put them all in one subsequence, since its length is 4, and 3 occurs more than 2 times. However, you can partition it into two subsequences: [3] and [2,3,3].

题意:给一段长为n的序列和m个询问,每个询问给一个[l,r],问最少可以把[l,r]分成几个子序列,使得自序列里面每一个元素的个数小于
⌈ \lceil 序 列 长 度 2 \frac{序列长度}{2} 2 ⌉ \rceil

用莫队可以维护区间内的出现个数最多的数的次数,具体在于双指针移动的时候,cnt[x]表示x这个数出现的个数,sum[cnt[x]]表示的是出现次数是cnt[x]的数有几个,当sum[cnt[x]]=0时就会对答案产生影响。
知道了最多元素的个数后,一个长为n的序列,假设最多元素出现的个数是max,那么最少需要分成几段呢?

因为涉及向上取整,因此组成的段,奇数长度越多越好,假设分为了t+1段,那最优策略为1 1 1 1 …(t个1)1 1与 n-t,这个时候能容纳出现数最多的个数是t+ ⌈ \lceil n − t 2 \frac{n-t}{2} 2nt ⌉ \rceil
因此可以有两种做法
第一种是二分,每次二分判断能容纳的数是否大于等于max
第二种推公式,这几个数当中有n-max个数不是出现最多的那个数,他们组一起能够容纳最多出现的数的个数就是n-max+1(很显然,比如假设有3个,那组成长为7的序列,能够容纳最多出现的数个数就是4),剩余的最多出现的数还剩max-(n-max+1),因为他们只能一个一个放,因此答案就是max(0,max-(n-max+1))+1。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define IOS ios::sync_with_stdio(0),cin.tie(0)
#define sf(a) cin>>a
#define dg(a) cout<<"#a"<<" "<<a<<endl
#define pi acos(double(-1))
using namespace std;
const int N=3e5+10;
int n,len,m,res;
struct Q
{
    int id,l,r;
}q[N];
int w[N],ans[N],cnt[N],sum[N];
int get(int x)
{
    return x/len;
}
bool cmp(struct Q a,struct Q b)
{
    int i=get(a.l),j=get(b.l);
    if(i!=j)return i<j;
    return a.r<b.r;
}
void add(int x)
{
    sum[cnt[x]]--;
    cnt[x]++;
    sum[cnt[x]]++;
    res=max(res,cnt[x]);
}
void del(int x)
{
    sum[cnt[x]]--;
    if(cnt[x]==res&&sum[cnt[x]]==0)res--;
    cnt[x]--;
    sum[cnt[x]]++;
}
bool check(int x,int lth)
{
    int num=x-1+(lth-x+1)/2+(lth-x+1)%2;
    if(num>=res)return true;
    return false;
}
int main ()
{
    sf(n),sf(m);
    len=sqrt(n);
    for(int i=1;i<=n;i++)sf(w[i]);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        sf(l),sf(r);
        q[i]={i,l,r};
    }
    sort(q+1,q+m+1,cmp);
    for(int k=1,i=0,j=1;k<=m;k++)
    {
        int l=q[k].l,r=q[k].r;
        while(i<r)add(w[++i]);
        while(i>r)del(w[i--]);
        while(j<l)del(w[j++]);
        while(j>l)add(w[--j]);
        /*binary answer
        r=r-l+1,l=1;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(check(mid,q[k].r-q[k].l+1))
            {
                r=mid;
            }
            else l=mid+1;
        }
        ans[q[k].id]=r;*/
        ans[q[k].id]=max(0,2*res-r+l-2)+1;
    }
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}

跑出来二分竟然比公式快,玄学…
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值