[分块] P3380 【模板】二逼平衡树(树套树)题解

前言

题目传送门

前置知识:分块,二分。

虽然本题是一个树套树,但在这里给出一个思维难度较低的分块算法。

思路

首先,看到这种排名或者前驱后继的问题我们第一眼想到权值线段树,但这是区间排名,需要可持久化再套一颗其他的树,为了避免思路太过复杂,我们考虑分块。

假设我们将整个序列分成 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=mid1,否则 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×5 104×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 1105+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.99sAC成绩

我长舒了一口气,算是没有功亏一篑。

最终代码:

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值