### SEI说树状数组不能处理区间最值?
众所周知,普通的树状数组处理区间和,都是运用容斥原理来完成运算的
但是,区间最值无法运用容斥原理来求,这怎么办呢?
### 不过,这就是很多人没有看到树状数组的本质了
我们来分析一下树状数组的节点:
f(1) for a(1)
f(2) for f(1)+a(2) = a(1)+a(2)
f(3) for a(3)
f(4) for f(2)+f(3)+f(4) = f(1~4)
f(5) ......
其中‘+’加号不一定是加法,也可用乘法,max,min等代替。
## 那么,我们可以发现什么?
对于f(x),它管辖的是(x-lowbit(x)+1)~x的所有元素,这也是树状数组最基本的定义。
也就是说,对于数x,我们求其的前缀和,就如同把其倍增地二进制分解为约莫log2(x)颗根节点范围为2^k的线段树,其中k为非负整数。
也就是倍增与分块。
那么,区间最值如何查询呢?
我们看到,由于前缀和是从1求起的,正好符合自然数的规律,所以分解为了多个2的非负整数次方的和。
那如果我们不让它从1求起呢?
当我们写树状数组时,经常会写到这种形式:
```cpp
......
int query(int x){
int sum=0;
while(x)
sum+=t[x],x-=lowbit(x);
return sum;
}
......
```
其中while循环与每次结束后的x-lowbit(x)等价于:
```cpp
for(;x>=1;x-=lowbit(x))
```
由于是前缀和,所以x>=1。
不难发现如果从下标2求起就是x>=2,从5求起就是x>=5,其中边界细节需要微调。
那么思路就很清晰了,如果求l~r区间的最值,循环即为(;r>=l;r-=lowbit(r)),其中一部分遍历到l之前,可以暂时不用树状数组,一格一格跳,等到遍历到管辖范围稍小的节点再用t[x]求值。
即:
```cpp
int query(int a,int b){
int sum=-INF;
while(b>=a){
sum=max(sum,f[b]),--b;
for(;b-lowbit(b)>=a;b-=lowbit(b))
sum=max(sum,t[b]);
}
return sum;
}
```
很优秀,时间复杂度最劣也不到2log2(r-l+1)。
但是对于[P1886](https://www.luogu.com.cn/problem/solution/P1886)单调队列这题的魔鬼数据还是会跪下,怎么办呢?
引进一个小优化,另sum数组:
```cpp
sum[i]=max(sum[i-1],a[i])
```
容易发现当sum[r]>sum[r-l]时,sum[r]即为l~r区间,也是所有数组下标<=r的元素中的最大值
我也不知道为什么,这个小优化省下了大量的时间。
本题AC代码如下:
```cpp
#include
#include
using namespace std;
const int maxn=1000001,INF=1145141919;
inline int read(){
int x=0,f=1; char ch=getchar();
while(ch<48||ch>57){if(ch=='-'){f=-1;}ch=getchar();}
while(ch>=48&&ch<=57){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a int n,m,k,l,r,f[maxn],t[maxn],f1[maxn],t1[maxn],s[maxn],s1[maxn];
int lowbit(int x){return x&-x;}
void update(int pos){
while(pos<=n){
t[pos]=f[pos],t1[pos]=f1[pos];
for(int i=1;i
pos+=lowbit(pos);
}
}
int query(int a,int b){
int sum=-INF;
while(b>=a){
sum=max(sum,f[b]),--b;
for(;b-lowbit(b)>=a;b-=lowbit(b))
sum=max(sum,t[b]);
}
return sum;
}
int query1(int a,int b){
int sum=INF;
while(b>=a){
sum=min(sum,f1[b]),--b;
for(;b-lowbit(b)>=a;b-=lowbit(b))
sum=min(sum,t1[b]);
}
return sum;
}
int main()
{
n=read(),k=read();
for(int i=1;i<=n;++i)
t1[i]=INF,t[i]=-INF;
s[0]=INF,s1[0]=-INF;
for(int i=1;i<=n;++i)
{
f[i]=f1[i]=read(),s[i]=min(s[i-1],f[i]),s1[i]=max(s1[i-1],f[i]);
update(i);
}
for(int i=k;i<=n;++i)
printf("%d ",s[i]!=s[i-k]?s[i]:query1(i-k+1,i));
putchar(10);
for(int i=k;i<=n;++i)
printf("%d ",s1[i]!=s1[i-k]?s1[i]:query(i-k+1,i));
return 0;
}
```
[P3865 ST表](https://www.luogu.com.cn/problem/P3865)同理。
第二篇题解,文章何处有误请见谅,望指正。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)