还是你们太牛了。

猫树

概述:类似于 ST表,可以支持快速查询大部分静态区间问题。

实现:考虑将询问分治,对于每次分治到的区间 \([l,r]\),处理出 \([l,mid]\) 中每个后缀的信息与 \((mid,r]\) 中每个前缀的信息。那么对于一个询问 \([L,R] (L \le mid < R)\),就可以通过合并前后缀信息来得到答案了。

举例:如果我们需要查询区间 \(\max\),那么由于 \(\max\limits_{i=L}^{R} a_{i} =\max(\max\limits_{i=L}^{mid}a_i ,\max\limits_{i=mid+1}^{R} a_i)\),所以答案就直接是 \(\max(rmax_L,lmax_R)\) 了。这样做的时间复杂度,由于一个点 \(i\) 只会在 \(O(\log)\) 个分治区间中被用到,所以是 \(O(T n\log n +qT)\) 的,其中 \(T\) 是合并信息的时间复杂度。

例题

SP1043 GSS1 - Can you answer these queries I

最大子段和。那么我们记录后缀最大子段和和前缀最大子段和,则答案就是 \(\max(rmax_L,lmax_R,rmax_L+lmax_R)\) 了。时间复杂度 \(O(n\log n+q)\)

怎么做到 \(O(1)\) 询问的?那么我们就需要快速找到一个区间 \([L,R]\) 在哪一次分治会被算到了。我们将每次的 \(mid\) 看作一个点,那么就相当于是 \(L,R\) 这两个叶子的 \(\operatorname{LCA}\) 对应的深度了。基于二叉树,\(L,R\)\(\operatorname{LCA}\) 是它们最长公共前缀。所以这个公共前缀的长度就是具体在哪次分治被算到了。

代码:

il void build(int u,int l,int r,int dep){
	if(l==r) return pos[l]=u,void(0);
	int mid=l+r>>1,s1=a[mid+1],s2=a[mid];
	lmax[dep][mid+1]=lmax_[dep][mid+1]=a[mid+1],
	rmax[dep][mid]=rmax_[dep][mid]=a[mid];
	for(re int i=mid+2;i<=r;++i){
		s1+=a[i];
		lmax[dep][i]=max(lmax[dep][i-1]+a[i],a[i]);
		lmax_[dep][i]=s1;
	}
	for(re int i=mid-1;i>=l;--i){
		s2+=a[i];
		rmax[dep][i]=max(rmax[dep][i+1]+a[i],a[i]);
		rmax_[dep][i]=s2;
	}
	for(re int i=mid+2;i<=r;++i){
		lmax[dep][i]=max(lmax[dep][i],lmax[dep][i-1]);
		lmax_[dep][i]=max(lmax_[dep][i],lmax_[dep][i-1]);
	}
	for(re int i=mid-1;i>=l;--i){
		rmax[dep][i]=max(rmax[dep][i],rmax[dep][i+1]);
		rmax_[dep][i]=max(rmax_[dep][i],rmax_[dep][i+1]);
	}
	build(ls(u),l,mid,dep+1),
	build(rs(u),mid+1,r,dep+1);
	return ;
}
il int query(int l,int r){
	if(l==r) return a[l];
	int dep=__[pos[l]]-__[pos[l]^pos[r]];
	return max({lmax[dep][r],rmax[dep][l],lmax_[dep][r]+rmax_[dep][l]});
}

il void solve(){
	n=rd,len=2;
	while(len<n) len<<=1;
	for(re int i=2;i<=(len<<1);++i) __[i]=__[i>>1]+1;
	for(re int i=1;i<=n;++i) a[i]=rd;
	build(1,1,len,1),m=rd;
	while(m--){
		int l=rd,r=rd;
		printf("%lld\n",query(l,r));
	}
    return ;
}

这里给一些注意事项:

  1. 需要将序列长度拓展为 \(2^k\),不然无法保证两点的 \(\operatorname{LCA}\) 求的是对的。
  2. 分治点的数量是 \(2n\) 级别的,所以可能用到的 \(\log_2 x\)\(x < 2n\)

P6240 好吃的题目

是一个区间背包问题,对于一次询问合并前缀后缀的背包即可。时间复杂度 \(O(nV\log n +qV)\)

P3246 [HNOI2016] 序列

这就不得不搬出 BZOJ4262 Sum 了,甚至是加强版。模拟赛的时候花费 2h 搓出类猫树,8kb 豪夺最长代码。然后 cf 场上直接套用,以 10kb 代码冲爆 *2700。你们还是太牛了。

题目是让求:\(\sum\limits_{l=L}^{R}\sum\limits_{r=l}^{R}\min\limits_{i=l}^{r} a_i\)

那么考虑区间中一个点 \(i\) 对答案的贡献。它应该是:\(a_i \times (\sum\limits_{l=L}^{i}\sum\limits_{r=i}^{R}[\min\limits_{j=l}^{r} a_j=a_i])\)。那么我们在分治的时候,对于当前分治区间中的每个 \(i\),就可以找到它前面第一个比它大的和后面第一个不比它小的,记为 \(x,y\)。这里的 \((x,y,i)\) 应该在 \(mid\) 的同一侧。然后分类讨论:

  1. \(L \le l \le r \le mid\),且 \(L \le i \le mid\)。那么 \(x < l \le i \le r < y\) 的区间 \([l,r]\) 都会是 \(i\) 来贡献。总的贡献为 \(a_i(i-x)(y-i)\)
  2. \(mid < l \le r \le R\),且 \(mid< i \le R\)。那么 \(x < l \le i \le r < y\) 的区间 \([l,r]\) 都会是 \(i\) 来贡献。总的贡献为 \(a_i(i-x)(y-i)\)
  3. \(L \le l \le mid < r \le R\)\(L \le i \le mid\)。如果 \(i\) 后面第一个不比它小的数在 \([L,mid]\),那么没有贡献。那么记 \(y'\) 为这个点的下标,则有 \(a_i (i-x)(y'-mid)\)
  4. \(L \le l \le mid < r \le R\)\(mid< i \le R\)。同上。

那么怎么快速处理出后缀和前缀信息呢。对于第 \(1\) 中情况,当 \(L > x\) 时,\(L \to L-1\) 都会产生 \(a_i(y-i)\) 的贡献。那么我们使用变量记录这些增量,并且在 \(L=x\) 时减去 \(a_i(y-i)\) 的增量即可;第 \(2\) 种情况同理。对于第 \(3\) 种情况,它的限制是两个区间了,我们将式子拆成:\(a_i(iy'-imid-xy'+xmid)=(a_ii)y'-(a_iimid)-(a_i)xy'+(a_imid)x\)。那么除了 \((a_i)xy'\) 以外都是可以仿照第 \(1\) 种情况处理的。\((a_i)xy'\) 的实际贡献是 \(a_i \max(x,L)\min(R,y')\)

posted @ 2025-04-27 18:06  harmis_yz  阅读(18)  评论(0)    收藏  举报