算法学习————猫树

解决问题:

猫树是解决无修改区间或树上询问的高效算法.

对于常规问题,比如区间最值,区间最大子段和。我们常常能用线段树等数据结构达到,构造 O(n),询问 O(logn)的时间复杂度。

对于这些做法,只有一点不好,询问复杂度 不够优秀,且对于一些特定问题,线段树的push_up合并也不好写。

但对于区间最值这类的问题,我们可以类似 RMQ 那样,在一般的问题上,以预处理的时间和空间,换取快速的询问。

算法流程

我们首先考虑询问一个区间[ql,qr]。如果ql=qr,就可以直接得到答案。否则会不断在线段树上定位,而且会在几个区间的中点 mid 处被分开。

考虑第一次被分开的位置,假设为p。那么原来的区间 [ql,qr] 就被分为[ql,mid],与[mid+1,qr] 。

我们考虑对于每一个 mid 预处理他向前的后缀 [i,mid] 的答案(其中(lleq ileq mid)),以及他向后的前缀 [mid+1,j](其中(mid+1leq jleq r))。

如果我们知道了p点所在的位置,我们可以直接利用之前预处理的[ql,mid]以及[mid+1,qr]的答案直接合并即可。

不难发现预处理的复杂度是(O(nlogn))的(对于每一层每个数刚好被考虑一次 (O(n) imes O(logn)=O(nlogn)))

然后怎么快速知道这个位置呢?

不难发现这个p的位置,就是线段树上对应[l,l]和[r,r]节点的lca(最近公共祖先)所处的位置,我们可以考虑用st表预处理,然后可以直接查询p的位置,但这样显然太麻烦了。

如果整棵线段树满足堆式存储(也就是对于点i,它的两个儿子分别为2i和2i+1),就有一个很好的性质。

对于任意两个同深度的点,他们的lca是他们二进制下lcp(最长公共前缀)。

我们把整棵树建成一个满二叉树[1,(2^k)],那么对于任意一个区间[i,i]都是满足他们的深度是最深且在同一层的。

注意对于不同深度的点不一定满足这个情况!!这就是为什么我们为什么要建满的原因。

然后对于两个数x,y的二进制下的lcp为 x >> Log2[x ^ y]。(这个很显然,丢掉第一个不相同的位后面的所有位就行了)

这样我们就可以实现询问(O(1))

例题:区间最长子段和

因为每一层的区间都是不重叠的,所以我们可以把每一层写进一个数组里

所以我们只需要预处理[i,mid]的最大前缀和与[mid+1,j]的最大前缀和,这个一边遍历一边取max。

以及这两个区间的最大子段和。至于那个最大子段和,可以利用前缀和相减,保存一个前缀和最小值就行了

因为无法注册SPOJ的账号,所以我也不知道写的对不对

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define B cout<<"Breakpoint"<<endl;
#define O(x) cout<<#x<<" "<<x<<endl;
#define o(x) cout<<#x<<" "<<x<<" ";
using namespace std;
int read(){
	int x = 1,a = 0;char ch = getchar();
	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
	return x*a;
}
const int maxn = 1e5+10;
int val[maxn],maxlen,m;
int ls(int x){return x << 1;}
int rs(int x){return x << 1 | 1;}
int Log2[maxn],n;
struct CatTree{
	int pos[maxn],Pre[maxn][30],Sum[maxn][30];
	void build(int x,int l,int r,int dep){
		if (l == r) {pos[l] = x;return;}	
		int mid = (l+r >> 1),sum,minn;
		Sum[dep][mid] = Pre[dep][mid] = sum = minn = val[mid];
		minn = min(minn,0);
		for (int i = mid-1;i >= l;i--){
			Pre[dep][i] = max(Pre[dep][i+1],sum += val[i]);
			Sum[dep][i] = max(Sum[dep][i+1],sum-minn);
			minn = min(minn,sum);
		}
		Sum[dep][mid+1] = Pre[dep][mid+1] = sum = minn = val[mid+1];
		minn = min(minn,0);
		for (int i = mid+2;i <= r;i++){
			Pre[dep][i] = max(Pre[dep][i-1],sum += val[i]);
			Sum[dep][i] = max(Sum[dep][i-1],sum-minn);
			minn = min(minn,sum);
		}
		build(ls(x),l,mid,dep+1),build(rs(x),mid+1,r,dep+1);
	}
	int query(int l,int r){
		if (l == r) return val[l];
		int dep = Log2[pos[l]]-Log2[pos[l]^pos[r]];
		return max(max(Sum[dep][l],Sum[dep][r]),Pre[dep][l]+Pre[dep][r]);
	} 
}T;
int main(){
	n = read();
	for (int i = 1;i <= n;i++) val[i] = read();
	maxlen = 1;
	while (maxlen < n) maxlen <<= 1;
	T.build(1,1,maxlen,1);
	for (int i = 2;i <= maxlen << 1;i++) Log2[i] = Log2[i >> 1]+1;
	m = read();
	for (int i = 1;i <= m;i++){
		int l = read(),r = read();
		printf ("%d
",T.query(l,r));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/little-uu/p/14743767.html