洛谷P5356 [Ynoi2017] 由乃打撲克
阿新 • • 發佈:2022-04-20
題目
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;
}