P5356 YNOI2017 由乃打扑克 (分块

本文介绍了如何解决P5356YNOI2017题目中区间加和区间第k大数的问题,通过数列分块和二分搜索策略,优化了查询效率,达到O(logw(n/B) * logB + B)的时间复杂度。博客详细探讨了散块处理和归并排序的应用,以及如何调整块大小以平衡性能和复杂度。

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

P5356 YNOI2017 由乃打扑克

题意:

区间加,区间第k大数

思路:

主席树又无法维护,考虑数列分块

  • 区间查询

    显然数列分块后直接统计答案的话,块的贡献是不独立且不正确的,一个块的 k k k小值不一定是另一个块内 k k k小值,线段树上求 k k k小本质是利用二分统计个数。所以我们也考虑二分答案,统计所有块小于等于该数的总数有多少个,假如小于k的话,二分的答案太小了,否则太大了。

    这样的话查询是 O ( l o g w ( n / B ∗ l o g B + B ) ) O(logw(n/B*logB+B)) O(logw(n/BlogB+B)),区间加归并调好块大小后,总复杂度为 O ( q n l o g n l o g w ) O(q\sqrt{nlogn}logw) O(qnlogn logw)

    很遗憾,被卡了(lxl yyds) 不过爷在情理之中

我们发现对于散块,可以事先归并成有序序列,这样二分的内部可以一次二分找出答案

单次查询优化为 O ( l o g w ( n / B ∗ l o g B ) + B ) O(logw(n/B*logB)+B) O(logw(n/BlogB)+B), 理论上取 B = n l o g n B=\sqrt{n}logn B=n logn复杂度最优,但是由于散块常数大,所以调小块大小跑的更快,这就是分块的魅力吧

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int L[maxn],R[maxn],a[maxn],b[maxn],p[maxn],n,m,big,num,pos[maxn],add[maxn],cnt1,cnt2,t1[maxn],t2[maxn],mn=2e5,mx=-2e5;
bool cmp(int x,int y){return a[x]<a[y];}
inline int read(){
    char c=getchar();int x=0,s=1;
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*s;
}
inline void in(int&x){
    x=read();
}
void init(){
    num=(n-1)/big+1;
    for(int i=1;i<=num;++i){
        L[i]=(i-1)*big+1;
        R[i]=i*big;
    }
    R[num]=n;
    for(int i=1;i<=num;++i){
        for(int j=L[i];j<=R[i];++j)
            pos[j]=i;
        sort(p+L[i],p+R[i]+1,cmp);
        for(int j=L[i];j<=R[i];++j)
            b[j]=a[p[j]];
    }
}
void rebuild(int x,int l,int r){
    cnt1=cnt2=0;
    for(int i=L[x];i<=R[x];++i){
        if(p[i]>=l&&p[i]<=r)t1[++cnt1]=p[i];
        else t2[++cnt2]=p[i];
    }
    merge(t1+1,t1+cnt1+1,t2+1,t2+1+cnt2,p+L[x],cmp);
    for(int i=L[x];i<=R[x];++i)b[i]=a[p[i]];
}
void update(int l,int r,int val){
    val>0?mx+=val:mn+=val;
    int pl=pos[l],pr=pos[r];
    if(pl==pr){
        for(int i=l;i<=r;++i)a[i]+=val;
        rebuild(pl,l,r);
    }else{
        for(int i=pl+1;i<=pr-1;++i)add[i]+=val;
        for(int i=l;i<=R[pl];++i)a[i]+=val;
        rebuild(pl,l,r);
        for(int i=L[pr];i<=r;++i)a[i]+=val;
        rebuild(pr,l,r);
    }
}
int ask(int l,int r,int w){
    int pl=pos[l],pr=pos[r];
    int sum=0;
    for(int i=pl+1;i<=pr-1;++i)
        sum+=upper_bound(b+L[i],b+R[i]+1,w-add[i])-b-L[i];
    sum+=upper_bound(t1+1,t1+cnt1+1,w-add[pl])-t1-1;
    sum+=upper_bound(t2+1,t2+cnt2+1,w-add[pr])-t2-1;
    return sum;
}
int query(int l,int r,int k){
    if(k>r-l+1)return -1;
    int pl=pos[l],pr=pos[r];
    cnt1=cnt2=0;
    if(pl==pr){
        for(int i=L[pl];i<=R[pl];++i)
            if(p[i]>=l&&p[i]<=r)t1[++cnt1]=b[i];
        return t1[k]+add[pl];
    }else{
        int l1=mn,r1=mx,mid,res;
        for(int i=L[pl];i<=R[pl];++i)
            if(p[i]>=l&&p[i]<=r)t1[++cnt1]=b[i];
        for(int i=L[pr];i<=R[pr];++i)
            if(p[i]>=l&&p[i]<=r)t2[++cnt2]=b[i];
        while(l1<=r1){
            mid=l1+r1>>1;
            int f=ask(l,r,mid);
            if(f>=k)res=mid,r1=mid-1;
            else l1=mid+1;
        }
        return res;
    }
}
int main(){
    in(n);in(m);
    big=sqrt(n)*log2(n)*0.17;
    big=max(1,big);
    for(int i=1;i<=n;++i)in(a[i]),p[i]=i,mx=max(mx,a[i]),mn=min(mn,a[i]);
    int op,l1,r1,k;
    init();
    for(int i=1;i<=m;++i){
        in(op);in(l1);in(r1);in(k);
        if(op==1)
            cout<<query(l1,r1,k)<<'\n';
        else
            update(l1,r1,k);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值