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

文章讲述了如何使用分块算法解决区间排名问题,通过预处理每个块内的数值并排序,利用二分查找优化查询效率。对于区间查询排名、前驱后继和区间第k小的操作,分别给出了相应的处理策略,同时讨论了时间复杂度和优化方法,包括块长的选择和常数优化。

前言

题目传送门

前置知识:分块,二分。

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

思路

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

假设我们将整个序列分成 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;
}
<think>嗯,用户问的是P3380模板树套树问题是否可以用分块方法解决。首先,我需要回忆一下P3380题目的具体要求。P3380是洛谷上的一个模板题,通常涉及动态区间第k大查询,可能还有插入、删除和修改操作。树套树,比如线段树套平衡树,是常见的解法,用于处理多维的查询问题。 用户提到分块方法,分块算法竞赛中常用于将问题分解为块,降低复杂度。比如莫队算法就是基于分块的。但动态区间第k大查询需要高效的数据结构支持,分块是否可行呢? 首先,分块处理区间问题的复杂度通常是根号级别的。对于每个块,可以维护一个有序数组。查询时,将区间分成块头和块尾的部分,以及中间的完整块。中间的完整块可以利用有序数组进行分查找,统计比某个值小的元素数量。这样,整体的时间复杂度大概是O(√n * log√n) 每次查询。对于修改操作,比如替换一个元素,需要更新所在块的有序数组,时间复杂度也是O(√n)因为需要重新排序块内的元素。 不过,这样的时间复杂度在数据量较大时可能无法通过时间限制,尤其是当操作次数达到1e5级别时,根号复杂度可能不够。相比之下,树套树的每次操作是O(log²n),在n=1e5时,log²n大约是几百,而根号n是300左右,所以分块可能勉强可行,但需要看具体题目的时间限制。 另外,分块实现起来相对简单,代码量可能比树套树少。对于不熟悉树套树的选手,分块可能更容易上手。但分块的时间复杂度较高,可能在极端数据下会超时。 然后,我需要考虑分块的具体实现细节。例如,每个块的大小设为√n,维护每个块的有序数组。对于查询第k大的元素,可以使用分答案的方法:在数值范围内分,每次猜测一个值mid,统计区间内<=mid的元素数量,根据这个数量调整分的上下界。每个统计操作需要遍历区间内的所有块,对每个块进行分查找,时间复杂度是O(√n * log n),加上外层分的次数大约是30次左右(比如数值范围是1e9的话),所以总的时间复杂度是O(30*√n * log n),这可能会比较高。 修改操作的话,比如替换一个位置的元素,需要找到对应的块,删除旧元素,插入新元素,并重新排序该块的有序数组。这一步的时间是O(√n),因为块的大小是√n,插入和删除需要线性时间,或者使用其他结构如平衡树来维护块内元素,但这样又会增加实现的复杂度。 此外,分块在处理动态插入和删除时可能不太方便,尤其是当需要保持块的大致大小时,可能需要动态调整块的大小,这会增加实现的难度。而原题P3380可能允许离线处理,或者没有插入删除操作,只有修改和查询,这时候分块可能可行。 再参考一些已有的资料或题解,是否有选手用分块通过该题。比如,洛谷的题解中,可能有分块解法。假设存在相关解法,可以引用作为参考。但如果没有,可能需要指出分块虽然理论可行,但实际可能不被推荐。 总结来说,分块方法可以解决P3380问题,但时间效率可能不如树套树或其它更高效的数据结构。适用于对时间要求不严格的情况,或者作为替代方案。同时需要注意分块实现的细节,比如块的大小调整、有序数组的维护方式等。 用户可能是在寻找替代树套树的更简单方法,或者想了解分块的应用场景。因此,回答时需要明确分块的可行性、时间复杂度和实现难度,对比树套树的优缺点,并给出建议。</think>关于P3380模板题(树套树)使用分块方法的可行性分析: ### 分块解决方案可行性 1. **问题特点** P3380要求实现动态区间第k大查询(带单点修改),传统树套树(如线段树套平衡树)的时间复杂度为$O(\log^2 n)$,而分块的时间复杂度为$O(\sqrt{n} \cdot \log n)$,理论上是可行的[^1]。 2. **分块实现思路** - **块内有序化**:将数组分为$\sqrt{n}$块,每块内部维护有序数组 - **分答案法**:通过数值范围分+块内分统计数量 - **单点修改**:重构所在块的有序数组 - **查询操作**: ```python def query_kth(l, r, k): low, high = min_val, max_val while low < high: mid = (low + high) // 2 cnt = count_less_equal(l, r, mid) if cnt < k: low = mid + 1 else: high = mid return low ``` 3. **复杂度对比** | 操作 | 树套树 | 分块方案 | |-----------|-------------|----------------| | 查询 | $O(\log^2 n)$ | $O(\sqrt{n} \log n)$ | | 修改 | $O(\log^2 n)$ | $O(\sqrt{n})$ | ### 实现代码框架 ```python class Block: def __init__(self, data): self.data = sorted(data) def update(self, pos, old_val, new_val): # 删除旧值并插入新值后重新排序 pass class BlockArray: def __init__(self, arr, block_size): self.blocks = [] for i in range(0, len(arr), block_size): self.blocks.append(Block(arr[i:i+block_size])) def query_kth(self, l, r, k): # 分答案结合块内统计 pass ``` ### 适用场景建议 1. **分块适用情况** - 数据规模$n \leq 10^5$且时限较宽松(如2s+) - 需要快速实现原型(代码量比树套树少约30%) 2. **推荐树套树的情况** - 严格时间复杂度要求(如$n \geq 10^6$) - 需要兼容其他操作(如前驱/后继查询)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值