CF716 D | Cut and Stick 线段树 | 莫队

本文详细介绍了如何使用线段树和莫队算法解决区间查询问题,特别是在处理数组元素分布和区间众数查询上的应用。线段树用于维护区间内出现频率最高的数,而莫队算法则能有效地进行离线区间众数计算。两种方法在处理动态查询时展现出了高效性,对于理解和应用数据结构与算法有着重要的参考价值。
摘要由CSDN通过智能技术生成

https://codeforces.ml/contest/1514/problem/D

题意

给定一个长度为n的数组以及q组询问,每组询问包括l和r。希望将a[l]~a[r]分为若干子集,使得每一子集(长度为x)所有元素出现个数都小于等于⌈ x 2 \frac{x}{2} 2x

思路

线段树:

  • 用线段树维护区间出现频率最大的数(不准确)
  • 对于一个数在[l,r]区间出现多少次可以用vector记录某个数在数组中的位置,用二分的方式就可以O(logn)得到答案(这个操作感觉还是实用的)
  • 通过mymax函数比较两个数哪个出现频率高,并返回出现频率高的数
  • 若出现次数f小于等于 ⌈ x 2 \frac{x}{2} 2x⌉,则返回1。否则,剩余元素为x-f,由于向上取整,最多可以和x-f+1个最大频率元素结合,因此返回f-(x-f+1)+1=2*f-x
    那么问题来了,线段树建树向上更新父节点时,难道父节点所代表的区间出现最高频率的数一定是两个儿子节点出现最高频率数中的一个吗?
    显然不是,例如2,2,2,3,3,1,1,1,3,3
    前半段出现最高频率数为2,后边段为1,而整段却为3
    那为什么又能这样写呢?
    若是父节点不是两个子节点之一,那么父节点的值出现次数一定小于等于⌈ x 2 \frac{x}{2} 2x⌉,因此对结果无影响

莫队
询问顺序无所谓,因此可以离线,看作一个区间众数问题,用莫队求出区间众数出现次数
主席树好像可以在线做,可惜我不会

代码

线段树

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=3e5+5;
const ll mod=1e9+7;
int a[maxn],tree[maxn<<2];
vector<int> vec[maxn];
int n,q;
int get(int x,int l,int r){
    return upper_bound(vec[x].begin(),vec[x].end(),r)-lower_bound(vec[x].begin(),vec[x].end(),l);
}
int mymax(ll x,ll y,ll l,ll r){
    ll t1=get(x,l,r),t2=get(y,l,r);
    if(t1>t2)return x;
    return y;
}
void build(int p,int l,int r){
    if(l==r){
        tree[p]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    build(2*p,l,mid);
    build(2*p+1,mid+1,r);
    tree[p]=mymax(tree[2*p],tree[2*p+1],l,r);
}
int query(int p,int l,int r,int x,int y){
    if(x<=l&&y>=r){
        return get(tree[p],x,y);
    }
    int mid=(l+r)>>1,t1=0,t2=0;
    if(x<=mid)t1=query(2*p,l,mid,x,y);
    if(y>mid)t2=query(2*p+1,mid+1,r,x,y);
    return max(t1,t2);
}
int main() {
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        vec[a[i]].pb(i);
    }
    build(1,1,n);

    while(q--){
        int l,r;cin>>l>>r;
        cout<<max(1,2*query(1,1,n,l,r)-(r-l+1))<<endl;
    }
}

莫队

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn=3e5+5;
struct query{
    int id,l,r,bl;
}q[maxn];
//num为数i出现次数,t为出现次数为i的数的个数,a为原数组
int num[maxn],a[maxn],t[maxn];
int ans[maxn];  //答案
int cnt,sz; //cnt是当前区间众数出现次数
bool cmp(query a,query b){
    return a.bl==b.bl?a.r<b.r:a.bl<b.bl;
}
void add(int i){
    t[num[a[i]]]--;
    num[a[i]]++;
    t[num[a[i]]]++;
    cnt=max(cnt,num[a[i]]);
}
void del(int i){
    t[num[a[i]]]--;
    if(cnt==num[a[i]]&&t[cnt]==0)cnt--;
    num[a[i]]--;
    t[num[a[i]]]++;
}
int main() {
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int n,m;cin>>n>>m;
    sz=sqrt(n);
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++){
        int l,r;cin>>l>>r;
        q[i]=(query){i,l,r,(l-1)/sz+1};
    }
    sort(q+1,q+m+1,cmp);
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        int ll=q[i].l,rr=q[i].r;
//        cout<<ll<<' '<<rr<<' '<<q[i].bl<<endl;
        while(l<ll)del(l++);
        while(l>ll)add(--l);
        while(r<rr)add(++r);
        while(r>rr)del(r--);
        ans[q[i].id]=max(2*cnt-(rr-ll+1),1);
    }
    for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

General.song

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值