前言
前置知识:分块,二分。
虽然本题是一个树套树,但在这里给出一个思维难度较低的分块算法。
思路
首先,看到这种排名或者前驱后继的问题我们第一眼想到权值线段树,但这是区间排名,需要可持久化再套一颗其他的树,为了避免思路太过复杂,我们考虑分块。
假设我们将整个序列分成 T T T 个块。由于要求前驱后继和区间第 k k k,我们不难想到,在预处理时,对每个块里面的数值进行排序,然后存在一个数组里面。
-
对于区间查询排名,散块直接暴力查找有多少小于它的,对于整块,在排好序的序列里,我们直接二分找到第一个大于等于它的位置,然后所有在这个位置之前的数都小于它,再将所有小于它的所有位置求一个和即可。
-
求区间第 k k k 小,我们可以先进行二分答案,在 c h e c k check check 时,我们可以先根据上面的方法找到小于这个数值的数有多少个。然后,如果这个数值 ≥ k \ge k ≥k,则说明这个数可行,并且大了,则 r = m i d − 1 r=mid-1 r=mid−1,否则 l = m i d + 1 l=mid+1 l=mid+1。特别注意,我们找到的是最小的大于 k k k 个数的值,而并非第 k k k 小,所有在算答案时,要减一。
-
单点修改。先在原数组上修改,然后直接放入排好序的数组中重新排序即可。
-
区间 k k k 的前驱。对于散块,找到最大的小于 k k k 的值。对于整块,通过在排好序的数组里二分查找,找到最大的小于 k k k 的值,然后对于所有算出来的值取一个 m a x max max 即可。
-
区间 k k k 的后继。对于散块,找到最小的大于 k k k 的值。对于整块,通过在排好序的数组里二分查找,找到最小的大于 k k k 的值,然后对于所有算出来的值取一个 m i n min min 即可。
可以发现,时间复杂度最高的是区间第 k k k 小操作,如果 m m m 个全是它,那么时间复杂度为 m n T log T log v + m T m \frac {n}{T}\log{T} \log {v}+mT mTnlogTlogv+mT,有均值不等式, T T T 取到 n log n \sqrt{n \log n} nlogn 时得到最小。(其中 v v v 为值域。)
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
int n,m;
int a[100005];
int add[1605],tot[100005],x[1605],y[1605];
int val[1605][1605];
inline char gc(){static char buf[1000010],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000010,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
inline void fast_read(re T&x){x=0;re bool f=0;static char s=gc();while(s<'0'||s>'9')f|=s=='-',s=gc();while(s>='0'&&s<='9')x=(x<<3)+(x<<1)+(s^48),s=gc();if(f)x=-x;}
static char buf[1000005];int len=-1;
inline void flush(){fwrite(buf,1,len+1,stdout);len=-1;}
inline void __PC(const char x){if(len==1000000)flush();buf[++len]=x;}
template<typename T>
inline void fast_write(re T x){if(x<0)x=-x,__PC('-');if(x>9)fast_write(x/10);__PC(x%10^48);}
#define fr fast_read
#define fw fast_write
#define fs flush
void init()
{
int len=n==1?1:sqrt(n*log2(n)),num=n/len;
// cout<<len<<endl;
for(int i=1;i<=num;++i)
{
x[i]=(i-1)*len+1,y[i]=i*len;
for(int j=x[i];j<=y[i];++j)
{
tot[j]=i;
val[i][j-x[i]+1]=a[j];
}
sort(val[i]+1,val[i]+len+1);
}
if(len*num<n)
{
x[num+1]=len*num+1,y[num+1]=n;
for(int i=len*num+1;i<=n;++i)
{
tot[i]=num+1;
val[num+1][i-len*num]=a[i];
}
sort(val[num+1]+1,val[num+1]+y[num+1]-x[num+1]+2);
}
}
int now[1605],nxt[1605];
int query(int l,int r,int k)
{
if(r-l+1<k) return -1;
int p=tot[l],q=tot[r];
if(p==q)
{
for(int i=l;i<=r;++i) now[i-l+1]=a[i];
sort(now+1,now+r-l+2);
return now[k]+add[p];
}
else
{
int ll=0,rr=0,mid,ans;
for(int i=l;i<=y[p];++i) ll=min(a[i]+add[p],ll),rr=max(a[i]+add[p],rr),now[i-l+1]=a[i]+add[p];
for(int i=x[q];i<=r;++i) rr=max(a[i]+add[q],rr),ll=min(a[i]+add[q],ll),nxt[i-x[q]+1]=a[i]+add[q];
for(int i=p+1;i<q;++i)
{
ll=min(val[i][y[i]-x[i]+1]+add[i],ll);
rr=max(val[i][y[i]-x[i]+1]+add[i],rr);
}
--ll,++rr;
sort(now+1,now+y[p]-l+2);
sort(nxt+1,nxt+r-x[q]+2);
while(ll<=rr)
{
mid=(ll+rr)>>1;
int noww=0;
int qwq=lower_bound(now+1,now+y[p]-l+2,mid)-now;
noww+=qwq-1;
qwq=lower_bound(nxt+1,nxt+r-x[q]+2,mid)-nxt;
noww+=qwq-1;
for(int i=p+1;i<q;++i)
{
qwq=lower_bound(val[i]+1,val[i]+y[i]-x[i]+2,mid-add[i])-val[i];
noww+=qwq-1;
}
if(noww>=k) rr=mid-1,ans=mid;
else ll=mid+1;
}
return ans-1;
}
}
void change(int l,int k)
{
int p=tot[l];
a[l]=k;
for(int i=x[p];i<=y[p];++i) val[p][i-x[p]+1]=a[i];
sort(val[p]+1,val[p]+y[p]-x[p]+2);
}
int query2(int l,int r,int k)
{
int p=tot[l],q=tot[r];
int res=0;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]<k) ++res;
return res+1;
}
else
{
for(int i=l;i<=y[p];++i) res+=a[i]<k;
for(int i=x[q];i<=r;++i) res+=a[i]<k;
for(int i=p+1;i<q;++i) res+=lower_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i]-1;
return res+1;
}
}
int front(int l,int r,int k)
{
int p=tot[l],q=tot[r];
int ans=-2147483647;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]<k) ans=max(ans,a[i]);
}
else
{
for(int i=l;i<=y[p];++i) if(a[i]<k) ans=max(ans,a[i]);
for(int i=x[q];i<=r;++i) if(a[i]<k) ans=max(ans,a[i]);
for(int i=p+1;i<q;++i)
{
int p=lower_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i]-1;
if(p==0) continue;
ans=max(ans,val[i][p]);
}
}
return ans;
}
int back(int l,int r,int k)
{
int p=tot[l],q=tot[r];
int ans=2147483647;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]>k) ans=min(ans,a[i]);
}
else
{
for(int i=l;i<=y[p];++i) if(a[i]>k) ans=min(ans,a[i]);
for(int i=x[q];i<=r;++i) if(a[i]>k) ans=min(ans,a[i]);
for(int i=p+1;i<q;++i)
{
int p=upper_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i];
if(p==y[i]-x[i]+2) continue;
ans=min(ans,val[i][p]);
}
}
return ans;
}
int main()
{
fr(n),fr(m);
for(int i=1;i<=n;++i)
{
fr(a[i]);
}
init();
while(m--)
{
int op,l,r,k;
fr(op);
if(op==1)
{
fr(l),fr(r),fr(k);
fw(query2(l,r,k));
__PC('\n');
}
else if(op==2)
{
fr(l),fr(r),fr(k);
fw(query(l,r,k));
__PC('\n');
}
else if(op==3)
{
fr(l),fr(k);
change(l,k);
}
else if(op==4)
{
fr(l),fr(r),fr(k);
fw(front(l,r,k));
__PC('\n');
}
else
{
fr(l),fr(r),fr(k);
fw(back(l,r,k));
__PC('\n');
}
}
fs();
return 0;
}
哎等等,先别走啊,你都不看看这份代码能得多少分吗。你发现,你得到了 T L E TLE TLE 80 80 80 分的好成绩。我们先来算一下在刚刚那种时间复杂度的情况下,程序大概需要运行 1 0 4 × 5 × 1 0 4 × 5 1 0 4 × 5 × log 1 0 4 × 5 × log 1 0 8 × log 1 0 4 × 5 × log 1 0 4 × 5 10^{4}\times 5 \times \frac{10^4\times 5}{\sqrt{10^4\times 5 \times \log {10^4\times 5}}}\times \log 10^8 \times \log{\sqrt{10^4\times 5\times \log{10^4\times 5}}} 104×5×104×5×log104×5104×5×log108×log104×5×log104×5 次,大概 6.8 × 1 0 8 6.8\times 10^8 6.8×108 次,显然对于某些极限数据过不了,所以我们考虑卡常。
卡常历程
首先,我发现第二个点和倒数第二个点 T T T 了,然后我觉得这个块长可以进行调节,于是我开始卡块长,最后卡到大概在 T = n + 480 T=\sqrt{n}+480 T=n+480 的位置上,得到了最快的 2.38 2.38 2.38 秒。
然后,我考虑常数优化,加上了一系列的 i n l i n e inline inline 和 r e g i s t e r register register,最终卡进了 2.20 2.20 2.20 秒,得到了 2.18 2.18 2.18 秒的好成绩。
这时,我猛然发现,这题并没有强制在线,并且他是直接单点赋值,而非增加,所以我考虑将所有东西输入完后离散化,这样二分的范围就可以控制在 1 − 1 0 5 + 1 1-10^5+1 1−105+1 以内,于是我便用这种技巧,卡过了第二个点,得到了 T L E TLE TLE 90 90 90 的好成绩。
然后我又测了一下倒数第 2 2 2 个点,发现他光荣的给我跑了 2.6 s 2.6s 2.6s 左右。并且我多次调整块长都没有太大进展,在将 T = n + 470 T=\sqrt{n}+470 T=n+470 和加了一些位运算的情况下卡进了 2.6 s 2.6s 2.6s。
接着,我又着手于常数优化,在操作 2 2 2 整块的二分上,先手动进行了一次二分,然后再用 l o w e r _ b o u n d lower\_bound lower_bound,竟然给我直接优化到了 2.48 s 2.48s 2.48s。
然后我想如果这倒数第二个点专门用来卡分块,那么他的修改操作肯定较少,所以我考虑把我算过的答案记录下来,在查询时如果记录过这个查询,则直接输出。这种玄学优化,又使得我来到了 2.34 s 2.34s 2.34s。
在各种卡常无果的情况下,我将整块的手动二分优化,带到了散块上面,这一下直接让我卡进了 2.20 s 2.20s 2.20s,得到了 2.19 s 2.19s 2.19s 的好成绩。我又用 c + + 20 c++20 c++20 交了一发,得到了 2.15 s 2.15s 2.15s 的好成绩。
我顿时看到了曙光。我先将数组压着开,然后将 r r r 在转换时,设成了小于 m i d mid mid 的上一个值,卡到了 2.12 s 2.12s 2.12s。
在万般困顿的最后,我又想起了老方法,调块长。我先将 T T T 调到了 n + 490 \sqrt{n}+490 n+490,然后得到了更接近一步的 2.06 s 2.06s 2.06s,然后在多次尝试之下,在最后的最后,当 T = n + 491 T=\sqrt{n}+491 T=n+491 时,我成功得到了 1.99 s 1.99s 1.99s 的 AC成绩。
我长舒了一口气,算是没有功亏一篑。
最终代码:
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<map>
using namespace std;
#define re register
// #define sort stable_sort*/
int n,m;
int a[50005];
int tot[50005],x[75],y[75];
int val[75][755];
int ls[100005],lslen;
inline char gc(){static char buf[100010],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,100010,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
inline void fast_read(re T&x){x=0;re bool f=0;static char s=gc();while(s<'0'||s>'9')f|=s=='-',s=gc();while(s>='0'&&s<='9')x=(x<<3)+(x<<1)+(s^48),s=gc();if(f)x=-x;}
static char buf[100005];int len=-1;
inline void flush(){fwrite(buf,1,len+1,stdout);len=-1;}
inline void __PC(const char x){if(len==100000)flush();buf[++len]=x;}
template<typename T>
inline void fast_write(re T x){if(x<0)x=-x,__PC('-');if(x>9)fast_write(x/10);__PC(x%10^48);}
#define fr fast_read
#define fw fast_write
#define fs flush
bool flg;
inline void init()
{
re int len=n==1?1:sqrt(n)+491,num=n/len;
// cout<<len<<endl;
for(int i=1;i<=num;++i)
{
x[i]=(i-1)*len+1,y[i]=i*len;
for(int j=x[i];j<=y[i];++j)
{
tot[j]=i;
val[i][j-x[i]+1]=a[j];
}
sort(val[i]+1,val[i]+len+1);
}
if(len*num<n)
{
x[num+1]=len*num+1,y[num+1]=n;
for(int i=len*num+1;i<=n;++i)
{
tot[i]=num+1;
val[num+1][i-len*num]=a[i];
}
sort(val[num+1]+1,val[num+1]+y[num+1]-x[num+1]+2);
}
}
int now[755],nxt[755];
map<pair<int,pair<int,int> >,int> uni;
inline int query(re const int l,re const int r,re const int k)
{
const int p=tot[l],q=tot[r];
if(p==q)
{
#pragma unroll(20)
for(int i=l;i<=r;++i) now[i-l+1]=a[i];
sort(now+1,now+r-l+2);
return ls[now[k]];
}
else
{
re int ll=1,rr=lslen+1,mid,ans;
#pragma unroll(20)
for(re int i=l;i<=y[p];++i) now[i-l+1]=a[i];
#pragma unroll(20)
for(re int i=x[q];i<=r;++i) nxt[i-x[q]+1]=a[i];
// --ll,++rr;
sort(now+1,now+y[p]-l+2);
sort(nxt+1,nxt+r-x[q]+2);
while(ll<=rr)
{
re int maxx=0;
mid=(ll+rr)>>1;
re int noww=0;
re int midd=(y[p]-l+2)>>1;
re int qwq;
if(now[midd]<mid) qwq=lower_bound(now+midd+1,now+y[p]-l+2,mid)-now-1;
else qwq=lower_bound(now+1,now+midd,mid)-now-1;
maxx=max(maxx,now[qwq]);noww+=qwq;
midd=(r-x[q]+2)>>1;
if(nxt[midd]<mid) qwq=lower_bound(nxt+midd+1,nxt+r-x[q]+2,mid)-nxt-1;
else qwq=lower_bound(nxt+1,nxt+midd,mid)-nxt-1;
maxx=max(maxx,nxt[qwq]);noww+=qwq;
#pragma unroll(20)
for(re int i=p+1;i<q;++i)
{
midd=(y[i]-x[i]+2)>>1;
if(val[i][midd]<mid)
qwq=lower_bound(val[i]+1+midd,val[i]+y[i]-x[i]+2,mid)-val[i]-1;
else
qwq=lower_bound(val[i]+1,val[i]+midd,mid)-val[i]-1;
maxx=max(maxx,val[i][qwq]);
noww+=qwq;
}
if(noww>=k)
{
rr=maxx,ans=maxx;
if(noww==k) break;
}
else ll=mid+1;
}
return ls[ans];
}
}
inline void change(const int l,const int k)
{
const int p=tot[l];
a[l]=k;
for(int i=x[p];i<=y[p];++i) val[p][i-x[p]+1]=a[i];
sort(val[p]+1,val[p]+y[p]-x[p]+2);
}
inline int query2(const int l,const int r,const int k)
{
const int p=tot[l],q=tot[r];
re int res=0;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]<k) ++res;
return res+1;
}
else
{
for(int i=l;i<=y[p];++i) res+=a[i]<k;
for(int i=x[q];i<=r;++i) res+=a[i]<k;
for(int i=p+1;i<q;++i) res+=lower_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i]-1;
return res+1;
}
}
inline int front(const int l,const int r,const int k)
{
re const int p=tot[l],q=tot[r];
re int ans=-2147483647;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]<k) ans=max(ans,a[i]);
}
else
{
for(int i=l;i<=y[p];++i) if(a[i]<k) ans=max(ans,a[i]);
for(int i=x[q];i<=r;++i) if(a[i]<k) ans=max(ans,a[i]);
for(int i=p+1;i<q;++i)
{
int p=lower_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i]-1;
if(p==0) continue;
ans=max(ans,val[i][p]);
}
}
if(ans>0)
return ls[ans];
else
return -2147483647;
}
inline int back(const int l,const int r,const int k)
{
const int p=tot[l],q=tot[r];
re int ans=2147483647;
if(p==q)
{
for(int i=l;i<=r;++i) if(a[i]>k) ans=min(ans,a[i]);
}
else
{
for(re int i=l;i<=y[p];++i) if(a[i]>k) ans=min(ans,a[i]);
for(re int i=x[q];i<=r;++i) if(a[i]>k) ans=min(ans,a[i]);
for(int i=p+1;i<q;++i)
{
int p=upper_bound(val[i]+1,val[i]+y[i]-x[i]+2,k)-val[i];
if(p==y[i]-x[i]+2) continue;
ans=min(ans,val[i][p]);
}
}
if(ans!=2147483647)
return ls[ans];
else
return 2147483647;
}
int op[50005],l[50005],r[50005],k[50005];
int main()
{
fr(n),fr(m);
for(re int i=1;i<=n;++i)
{
fr(a[i]);
ls[++lslen]=a[i];
}
for(re int i=1;i<=m;++i)
{
fr(op[i]);
if(!(op[i]^1))
{
fr(l[i]),fr(r[i]),fr(k[i]);
ls[++lslen]=k[i];
}
else if(!(op[i]^2))
{
fr(l[i]),fr(r[i]),fr(k[i]);
}
else if(!(op[i]^3))
{
fr(l[i]),fr(k[i]);
ls[++lslen]=k[i];
}
else if(!(op[i]^4))
{
fr(l[i]),fr(r[i]),fr(k[i]);
ls[++lslen]=k[i];
}
else
{
fr(l[i]),fr(r[i]),fr(k[i]);
ls[++lslen]=k[i];
}
}
sort(ls+1,ls+lslen+1);
lslen=unique(ls+1,ls+lslen+1)-ls-1;
for(re int i=1;i<=n;++i) a[i]=lower_bound(ls+1,ls+lslen+1,a[i])-ls;
init();
for(re int i=1;i<=m;++i)
{
if(op[i]^2)
k[i]=lower_bound(ls+1,ls+lslen+1,k[i])-ls;
if(!(op[i]^1))
{
fw(query2(l[i],r[i],k[i]));
__PC('\n');
}
else if(!(op[i]^2))
{
if(uni.count(make_pair(l[i],make_pair(r[i],k[i])))&&!flg) fw(uni[make_pair(l[i],make_pair(r[i],k[i]))]),__PC('\n');
else
{
re int qwq=query(l[i],r[i],k[i]);
if(!flg) uni[make_pair(l[i],make_pair(r[i],k[i]))]=qwq;
fw(qwq);
__PC('\n');
}
}
else if(!(op[i]^3))
{
flg=1;
change(l[i],k[i]);
}
else if(!(op[i]^4))
{
fw(front(l[i],r[i],k[i]));
__PC('\n');
}
else
{
fw(back(l[i],r[i],k[i]));
__PC('\n');
}
}
fs();
return 0;
}