算法学习————猫树
解决问题:
猫树是解决无修改区间或树上询问的高效算法.
对于常规问题,比如区间最值,区间最大子段和。我们常常能用线段树等数据结构达到,构造 O(n),询问 O(logn)的时间复杂度。
对于这些做法,只有一点不好,询问复杂度 不够优秀,且对于一些特定问题,线段树的push_up合并也不好写。
但对于区间最值这类的问题,我们可以类似 RMQ 那样,在一般的问题上,以预处理的时间和空间,换取快速的询问。
算法流程
我们首先考虑询问一个区间[ql,qr]。如果ql=qr,就可以直接得到答案。否则会不断在线段树上定位,而且会在几个区间的中点 mid 处被分开。
考虑第一次被分开的位置,假设为p。那么原来的区间 [ql,qr] 就被分为[ql,mid],与[mid+1,qr] 。
我们考虑对于每一个 mid 预处理他向前的后缀 [i,mid] 的答案(其中\(l\leq i\leq mid\)),以及他向后的前缀 [mid+1,j](其中\(mid+1\leq j\leq r\))。
如果我们知道了p点所在的位置,我们可以直接利用之前预处理的[ql,mid]以及[mid+1,qr]的答案直接合并即可。
不难发现预处理的复杂度是\(O(nlogn)\)的(对于每一层每个数刚好被考虑一次 \(O(n)\times 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\n",T.query(l,r));
}
return 0;
}