分块,最最基本的根号算法。
或许在有些时候比不过线段树、树状数组,RMQ也比不过ST表,但是可以骗分
比较实用的算法
这一篇只讲解最值问题
一、引入
常常会看到这样的问题:
给点一个数列 a a a ,求 l l l 到 r r r 的最大值/最小值(洛谷P3865)
我会st表这还不随手切掉
我会线段树
看标题,今天讲更暴力一点的分块
暴力的思考,设变量
a
n
s
ans
ans 初始为
a
l
a_l
al 然后从
a
l
+
1
a_{l+1}
al+1 枚举到
a
r
a_r
ar ,如果有比
a
n
s
ans
ans 大的就赋值
//代码口胡的,应该是对的
int ans=a[l];
for(int i=l+1;i<=r;i++){
ans=max(ans,a[i]);
}
有什么问题?
当然有问题
m
m
m 次查询,慢的和狗一样,复杂度
O
(
n
m
)
O(nm)
O(nm) ,想想优化
假设数列
a
a
a 为下图
定义长度为
l
e
n
len
len ,此时就为10
我们可以把这个数列分成若干个块
均摊下来,
n
\sqrt{n}
n 个块应该是比较好的,最后面大概率会多出一个角块,除了这个角块以外的所以块长度都为
n
\sqrt{n}
n
输入时就可以预处理每一个
a
i
a_i
ai 所在的块以及每一块的最大值
用
k
i
k_i
ki 表示
i
i
i 所在的块,
m
x
i
mx_i
mxi 表示第
i
i
i 块的最大值
int bl=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
k[i]=(i-1)/bl+1;//
mx[k[i]]=max(a[i],mx[k[i]]);
}
此时分为两种情况
1.
l
l
l
r
r
r位于同一个或相邻的块中,此时用之前的暴力算法处理即可,最劣也只是
2
n
2\sqrt{n}
2n 罢了
2.中间隔了至少一个块
那么就可以处理两边的边角部分再枚举中间的每一块,记录最大值
最劣应该是
3
n
3\sqrt{n}
3n ,扛得住的
部分解释在代码里
有的人在预处理时还会有
l
l
l 和
r
r
r 数组记录每个块的开始和结束,但是我这个写法就不用啦
code:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int k[1114514],a[1114514],mx[1114514];
int n,m;
int main(){
ios::sync_with_stdio(false);
n=read();m=read();
int bl=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
k[i]=(i-1)/bl+1;
mx[k[i]]=max(a[i],mx[k[i]]);
}
int l,r;
while(m--){
l=read(),r=read();
int ans;ans=INT_MIN;
if(k[l]==k[r] or k[l]+1==k[r]){//假设在同一个块里头
for(int i=l;i<=r;i++){
ans=max(ans,a[i]);
}
cout<<ans<<'\n';
}
else{//不在
int ll=k[l],rr=k[r];//最左和最右的块
for(int i=l;k[i]==ll;i++){//一直枚举,直到这个点不再属于最左边那一块
ans=max(ans,a[i]);
}
for(int i=r;k[i]==rr;i--){//一直枚举,直到这个点不再属于最右边那一块
ans=max(ans,a[i]);
}
for(int i=ll+1;i<rr;i++){//枚举中间的块的最大值
ans=max(ans,mx[i]);
}
cout<<ans<<'\n';//done
}
}
return 0;
}
最小值与最大值同理,不赘述了