引言
今天主要还是练了两道题,是有关线段树如何去求一个区间内的最值问题的,我们可以用线段树来解决。
对应一个无法改变顺序的数组,我们想要去求一个区间内的最值,假设有n个结点,m次询问,暴力的解决办法的时间复杂度为O(m*n),当结点的个数很大时,对时间的花销太大了,因此我们可以考虑用线段树来解决问题,时间复杂度仅为O(m*logn),也许仅这样看你看不出来线段树的优势,换句话说,要查一个区间长度为40亿的区间,仅需要不到33次就可以查完
因此,我们今天的主题就是如何用线段树来解决区间内的最值问题(最大值,最小值)
例题
1.求区间内的最小值
P1816 忠诚
思路:对于这种区间问题,我们在建树方面就会有所不同,我们首先在建树的时候,sum中指的不在是区间中的权值,而是区间中的最值,对于某个结点,我们的sum中存储的就是这个区间里面的最值
然后在查区间的时候也会有所不同,我们需要判断区间是否在左子树或者右子树,以及处理区间在左右子树都有重叠位置
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int num[100005];
int x,y;
struct node{
int l;
int r;
int sum;
}tree[100005*4];
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
if(l==r)
{
tree[i].sum=num[l];
return ;
}
int mid=(l+r)/2;
build(i*2,l,mid);
build(i*2+1,mid+1,r);
tree[i].sum=min(tree[i*2].sum,tree[i*2+1].sum);//存储子树中最小的那个
return ;
}
int find(int i,int l,int r)
{
if(tree[i].l>=l&&tree[i].r<=r)
{
return tree[i].sum;
}
int mid=(tree[i].l+tree[i].r)/2;
if(mid>=r)//处于左子树
{
return find(i*2,l,r);
}
if(mid<l)//处于右子树
{
return find(i*2+1,l,r);
}
return min(find(i*2,l,mid),find(i*2+1,mid+1,r));//两边都有涉及
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>num[i];
}
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>x>>y;
cout<<find(1,x,y)<<" ";
}
return 0;
}
2.求区间内的最大值
P1531 I Hate It
思路:很明显的单点修改的线段树的求区间最大值问题,我们在修改单点最大值的时候也要回退去修改其余相关区间内的最大值,查询和建树和上面的那个最小值差不多
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int num[200005];
char s;
int a,b;
struct node{
int l;
int r;
int sum;
}tree[200005*4];
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
if(l==r)
{
tree[i].sum=num[l];
return ;
}
int mid=(l+r)/2;
build(i*2,l,mid);
build(i*2+1,mid+1,r);
tree[i].sum=max(tree[i*2].sum,tree[i*2+1].sum);
return ;
}
void add(int i,int dis,int k)
{
if(tree[i].l==tree[i].r)
{
tree[i].sum=k;
return ;
}
int mid=(tree[i].l+tree[i].r)/2;
if(mid>=dis)
{
add(i*2,dis,k);
}
else
{
add(i*2+1,dis,k);
}
tree[i].sum=max(tree[i*2].sum,tree[i*2+1].sum);
return ;
}
int find(int i,int l,int r)
{
if(tree[i].l>=l&&tree[i].r<=r)
{
return tree[i].sum;
}
int mid=(tree[i].l+tree[i].r)/2;
if(r<=mid)
{
return find(i*2,l,r);
}
if(l>mid)
{
return find(i*2+1,l,r);
}
return max(find(i*2,l,mid),find(i*2+1,mid+1,r));
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>num[i];
}
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>s;
cin>>a>>b;
if(s=='Q')
{
cout<<find(1,a,b)<<"\n";
}
else
{
if(num[a]<b)
{
num[a]=b;
add(1,a,b);
}
}
}
return 0;
}
总结
线段树去求区间最值问题,一般用于区间内的数没有顺序,不能使用二分的时候可以考虑使用线段树来求最值问题,时间复杂度在查询和修改的时候也为O(logN)可以很大节约时间开销
同时在其中建树的时候也是有讲究的,其中的sum为区间内的最值
建树代码
求区间最大值:
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
if(l==r)
{
tree[i].sum=num[l];
return ;
}
int mid=(l+r)/2;
build(i*2,l,mid);
build(i*2+1,mid+1,r);
tree[i].sum=max(tree[i*2].sum,tree[i*2+1].sum);
return ;
}
求区间最小值:
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
if(l==r)
{
tree[i].sum=num[l];
return ;
}
int mid=(l+r)/2;
build(i*2,l,mid);
build(i*2+1,mid+1,r);
tree[i].sum=min(tree[i*2].sum,tree[i*2+1].sum);
return ;
}