洛谷P5356 [Ynoi2017] 由乃打扑克

题目

https://www.luogu.com.cn/problem/P5356

思路

由乃题,那么考虑分块(大雾,但确实分块是正解)。

题面很清晰,就是求动态的区间第k小,支持区间加法操作。

根据套路,需要维护一个原数组a,每个块的加法标记add,还有对原数组进行块内排序的结果c。

考虑到第k小这个东西不好维护,对于查询操作我们在外面套一层二分,来二分答案x,统计区间内小于x的值的个数。

那这个东西就好算了,每个块内部从小到大排序,对于整块的调用一下\(lower\)_\(bound\),两边的零头暴力统计就好(注意暴力时访问的是a数组)。

对于区间加法操作,整块的显然维护一下块的加法标记就好,因为每个数加的一样不改变次序。零头比较麻烦,因为可能会破坏所在块的有序性。

那我们直接暴力重构这些块,考虑到一次区间加法操作最多有两个块需要重构,代价可以接受(事实上更好的做法是记录该块需要重构,查询操作遇到该块时才真正重构它)。

一些细节就直接看代码吧,语言能力不好表述不清楚。。。

代码(version1)

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define inf 0x3f3f3f3f
#define maxn 100010
using namespace std;
int a[maxn],block,add[maxn],b[maxn],cnt,unset[maxn];
int L[maxn],R[maxn];
int c[maxn],MAX=-inf,MIN=inf;
void reset(int x){
    int i;
    for(i=L[x];i<=R[x];++i) a[i]+=add[x];
    for(i=L[x];i<=R[x];++i) c[i]=a[i];
    sort(c+L[x],c+R[x]+1);
    add[x]=0;
    unset[x]=0;
    return;
}
int Count(int l,int r,int k){
    int i,ans=0;
    if(b[l]==b[r]){
        for(i=l;i<=r;++i) if(a[i]+add[b[i]]<k) ans++;
        return ans;
    }
    for(i=b[l]+1;i<b[r];++i){
        if(unset[i]) reset(i);
        ans+=lower_bound(c+L[i],c+R[i]+1,k-add[i])-(c+L[i]);
    }
    for(i=l;i<=R[b[l]];++i) ans+=(a[i]+add[b[i]]<k);
    for(i=L[b[r]];i<=r;++i) ans+=(a[i]+add[b[i]]<k);
    return ans;
}
int query(int l,int r,int k){
    int x=MIN,y=MAX;
    if(k>r-l+1) return -1;
    while(x<y){
        int mid=(x+y+1)>>1;
        int t=Count(l,r,mid);
        if(t>k-1) y=mid-1;
        else x=mid;
    }
    return x;
}
void modify(int l,int r,int k){
    int i,j;
    if(k>0) MAX+=k;
    if(k<0) MIN+=k;
    if(b[l]==b[r]){
        for(i=l;i<=r;++i) a[i]+=k;
        unset[b[l]]=1;
        return;
    }
    for(i=b[l]+1;i<b[r];++i) add[i]+=k;
    for(i=l;i<=R[b[l]];++i) a[i]+=k;
    for(i=L[b[r]];i<=r;++i) a[i]+=k;
    unset[b[l]]=unset[b[r]]=1;
}
int main(){
    int n,m,i,j,opt,x,l,r;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i){
        scanf("%d",&a[i]);
        MAX=max(MAX,a[i]);
        MIN=min(MIN,a[i]);
    }
    block=max(2,(int)sqrt(0.6*n));
    for(i=1;i<=n;++i) b[i]=(i-1)/block+1;
    cnt=b[n];
    for(i=1;i<=n;++i) c[i]=a[i];
    for(i=1;i<=cnt;++i) L[i]=(i-1)*block+1,R[i]=min(i*block,n);
    for(i=1;i<=cnt;++i) sort(c+L[i],c+R[i]+1);
    for(i=1;i<=m;++i){
        scanf("%d%d%d%d",&opt,&l,&r,&x);
        if(opt==1) printf("%d\n",query(l,r,x));
        else modify(l,r,x);
    }
    return 0;
}

优化

然后我们发现这个代码T飞了,粗略估计一下,这个东西复杂度是鬼畜的\(m\sqrt nlognlog(二分范围)\),再考虑人傻常数大的因素明显会寄。

当时我自己调的时候想法就是减少二分范围,维护一个不紧的上下界,然后再调整一下块长,可是死活过不去。

然后贺一波题解,发现了一个很玄学的优化:

考虑到数的值域很小,那么很有可能块内每个数的大小都差不多。这意味着什么呢?这就是说,二分的时候,很可能一整个块的数都整体大于或整体小于二分的值\(x\)

于是我们判一下块头元素和块尾元素,如果头比\(x\)大或尾比\(x\)小就可以直接跳过二分的过程。

加上玄学优化就跑得很快。

代码(version2)

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define inf 0x3f3f3f3f
#define maxn 100010
using namespace std;
int a[maxn],block,add[maxn],b[maxn],cnt,unset[maxn];
int L[maxn],R[maxn];
int c[maxn];
void reset(int x){
    int i;
    for(i=L[x];i<=R[x];++i) a[i]+=add[x];
    for(i=L[x];i<=R[x];++i) c[i]=a[i];
    sort(c+L[x],c+R[x]+1);
    add[x]=0;
    unset[x]=0;
    return;
}
int Count(int l,int r,int k){
    int i,ans=0;
    if(b[l]==b[r]){
        for(i=l;i<=r;++i) if(a[i]+add[b[i]]<k) ans++;
        return ans;
    }
    for(i=b[l]+1;i<b[r];++i){
        if(c[L[i]]>=k-add[i]) continue;
        if(c[R[i]]<k-add[i]){
            ans+=R[i]-L[i]+1;
            continue;
        }
        ans+=lower_bound(c+L[i],c+R[i]+1,k-add[i])-(c+L[i]);
    }
    for(i=l;i<=R[b[l]];++i) ans+=(a[i]+add[b[i]]<k);
    for(i=L[b[r]];i<=r;++i) ans+=(a[i]+add[b[i]]<k);
    return ans;
}
int query(int l,int r,int k){
    int x=inf,y=-inf;
    for(int i=b[l];i<=b[r];++i){
        if(unset[i]) reset(i);
        x=min(x,c[L[i]]+add[i]);
        y=max(y,c[R[i]]+add[i]);
    }
    if(k>r-l+1) return -1;
    while(x<y){
        int mid=(x+y+1)>>1;
        int t=Count(l,r,mid);
        if(t>k-1) y=mid-1;
        else x=mid;
    }
    return x;
}
void modify(int l,int r,int k){
    int i,j;
    if(b[l]==b[r]){
        for(i=l;i<=r;++i) a[i]+=k;
        unset[b[l]]=1;
        return;
    }
    for(i=b[l]+1;i<b[r];++i) add[i]+=k;
    for(i=l;i<=R[b[l]];++i) a[i]+=k;
    for(i=L[b[r]];i<=r;++i) a[i]+=k;
    unset[b[l]]=unset[b[r]]=1;
}
int main(){
    int n,m,i,j,opt,x,l,r;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i) scanf("%d",&a[i]);
    block=max(2,(int)(sqrt(n*0.7)));
    for(i=1;i<=n;++i) b[i]=(i-1)/block+1;
    cnt=b[n];
    for(i=1;i<=n;++i) c[i]=a[i];
    for(i=1;i<=cnt;++i) L[i]=(i-1)*block+1,R[i]=min(i*block,n);
    for(i=1;i<=cnt;++i) sort(c+L[i],c+R[i]+1);
    for(i=1;i<=m;++i){
        scanf("%d%d%d%d",&opt,&l,&r,&x);
        if(opt==1) printf("%d\n",query(l,r,x));
        else modify(l,r,x);
    }
    return 0;
}
posted @ 2022-04-20 15:12  文艺平衡树  阅读(101)  评论(0编辑  收藏  举报