CF1514-D. Cut and Stick【莫队/主席树/线段树/随机数】

11 篇文章 0 订阅
7 篇文章 0 订阅

题意

给出一个序列 a a a,长度为 n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 ) n(1\leq n \leq 3· 10^5) n(1n3105)。给出 q ( 1 ≤ n ≤ 3 ⋅ 1 0 5 ) q(1\leq n \leq 3· 10^5) q(1n3105) 个询问,每次选择一个区间 [ l , r ] [l,r] [l,r] ,问最少要将该区间拆分为几个子序列,使得每个序列中出现次数最多的数出现的次数不大于 ⌈ l e n 2 ⌉ \lceil \frac{len}{2}\rceil 2len,其中 l e n len len 为序列长度。

分析

假设序列长度为 a a a,出现次数最多的数出现的次数为 b b b,当 b > ⌈ a 2 ⌉ b>\lceil \frac{a}{2}\rceil b>2a,需要进行拆分。为了使原序列符合要求,每次应该选择出现次数最多的数拿出去,单独组成一个长度为 1 1 1 的序列。假设拿出了 x x x 个,有 b − x ≤ a − x + 1 2 b-x\leq \frac{a-x+1}{2} bx2ax+1,即 x ≥ 2 b − a − 1 x\geq 2b-a-1 x2ba1 x x x 取最小值,得到的序列个数的最小值为 x + 1 = 2 b − a x+1=2b-a x+1=2ba。由此,问题转化为求出区间中出现次数最多的数出现的次数。可以利用主席树、线段树、随机数的方法(莫队不会QAQ)。

对于主席树,需要维护每个数出现的次数,因为最大值不能进行加减,但本题的特殊之处在于我们需要找出出现次数大于 ⌈ l e n 2 ⌉ \lceil \frac{len}{2}\rceil 2len 的数的出现次数,通过判断区间中出现的数字的个数个目标的关系来判断。

对于线段树,每个点维护所表示区间中出现次数最大的数是哪个。需要提前预处理出每个数出现的位置,在区间合并时,只要判断两个儿子节点在父节点表示区间内谁出现次数更多,取大的即可。询问的时候记得放回出现次数。

随机数的方法,对于一段区间,出现次数大于 ⌈ l e n 2 ⌉ \lceil \frac{len}{2}\rceil 2len的数的个数一定占所有数的 1 2 \frac{1}{2} 21,每次随机选一个数,不是要求数的概率为 1 2 \frac{1}{2} 21,多选几次,使得 ( 1 2 ) k (\frac{1}{2})^k (21)k 尽量小,总有一次会选中。

代码

主席树代码:

#include <cstdio>
#include <algorithm>
#include <vector>
#include <iostream>
#define pb push_back
using namespace std;
const int N=3e5+5;
int a[N];
int tree[N*40],lson[N*40],rson[N*40];
int root[N],tol;
void update(int l,int r,int pos,int &rt,int last){
    rt=++tol;
    tree[rt]=tree[last]+1;
    lson[rt]=lson[last];
    rson[rt]=rson[last];
    if(l==r) return;
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,pos,lson[rt],lson[last]);
    else update(mid+1,r,pos,rson[rt],rson[last]);
}
int query(int l,int r,int u,int v,int val){
    if(tree[v]-tree[u]<=val) return 0;
    if(l==r) return tree[v]-tree[u];
    int mid=(l+r)>>1;
    //int num=tree[lson[v]]-tree[lson[u]];
    if(tree[lson[v]]-tree[lson[u]]>val) return query(l,mid,lson[u],lson[v],val);
    else if(tree[rson[v]]-tree[rson[u]]>val) return query(mid+1,r,rson[u],rson[v],val);
    else return 0;
}
int main(){
    int n,q;
    scanf("%d%d",&n,&q);
    tol=0;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        update(1,n,x,root[i],root[i-1]);
    }
    while(q--){
        int l,r;
        scanf("%d%d",&l,&r);
        int a=r-l+1;
        int b=query(1,n,root[l-1],root[r],(a+1)/2);
        printf("%d\n",max(1,2*b-a));
    }
    return 0;
}

线段树代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <vector>
#define pb push_back
using namespace std;
const int N=3e5+5;
int a[N];
int tree[N<<2];//该点表示的区间内出现次数最多的数是哪个
vector<int>pos[N];
int cal(int x,int l,int r){
    if(x==0) return 0;
    return upper_bound(pos[x].begin(),pos[x].end(),r)-lower_bound(pos[x].begin(),pos[x].end(),l);
}
void pushup(int rt,int l,int r){
    tree[rt]=cal(tree[rt<<1],l,r)>cal(tree[rt<<1|1],l,r)?tree[rt<<1]:tree[rt<<1|1];
}
void build(int l,int r,int rt){
    if(l==r){
        tree[rt]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    pushup(rt,l,r);
}
int query(int l,int r,int L,int R,int rt){
    if(L<=l&&r<=R) return cal(tree[rt],L,R);
    int mid=(l+r)>>1,ans=0;
    if(L<=mid) ans=max(ans,query(l,mid,L,R,rt<<1));
    if(R>mid) ans=max(ans,query(mid+1,r,L,R,rt<<1|1));
    return ans;
}
int main(){
    int q,n;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        pos[a[i]].pb(i);
    }
    build(1,n,1);
    while(q--){
        int l,r;
        scanf("%d%d",&l,&r);
        int x=r-l+1;
        int y=query(1,n,l,r,1);
        if(y>(x+1)/2) printf("%d\n",max(1,2*y-x));
        else printf("1\n");
    }
    return 0;
}

随机数解法代码:

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <random>
#include <chrono>
#define pb push_back

using namespace std;
const int N=3e5+5;
int a[N];
vector<int>pos[N];
int main(){
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        pos[a[i]].pb(i);
    }
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());//随机数种子
    while(q--){
        int l,r,num=0;
        scanf("%d%d",&l,&r);
        int len=r-l+1,maxn=0;
        for(int i=1;i<=40;i++){
            int x=a[uniform_int_distribution<int>(l,r)(rng)];
            num=upper_bound(pos[x].begin(),pos[x].end(),r)-lower_bound(pos[x].begin(),pos[x].end(),l);
            maxn=max(num,maxn);
        }
        printf("%d\n",max(1,2*maxn-len));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值