1. 程式人生 > 其它 >洛谷P5356 [Ynoi2017] 由乃打撲克

洛谷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;
}