RMQ算法例题(线段树写法)及线段树初学理解
上次学的ST算法解决线段树写法,然后发现线段树其实也可以解决这个问题,不知道为什么我对树有一点阴影,一开始我一直不敢学呜呜呜,还是得慢慢一点点前进!
例题: balanced upline
有n头母牛 接下来一行有n个数表示每头母牛的高度,他们的位置是1-n。
给出T组查询区间,找出区间中(含端点)最小的母牛高度。
如果仅仅是这样,用ST写法可以很快的求出,属于直接套模板的题,稍微给自己加点难度,(正好试着学一下线段树),放一个修改操作。
稍改后的题:
有n头母牛 接下来一行有n个数表示每头母牛的高度,他们的位置是1-n。
现有两个操作分别用0,1表示。
操作0:给出两个数l,r,求出区间【l,r】间最小的母牛高度并输出;
操作1:给出两个数pos,val,将第pos只母牛高度修改成val。
有T行操作,每行操作含三个数,第一个数表示操作。
n<=1e6,1<=l<=r<=n,1<=pos<=n,val<=1e6;
样例输入
5
40 10 20 30 2
6
0 4 5
0 2 4
1 4 6
0 3 4
1 3 4
1 3 20
样例输出
2
10
6
啥也不说,先给代码:
/*the trees will never defeat me forever,
for all the trees is the scenery on the ACM roads
————luxun
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#define LL long long
#define pb push_back
using namespace std;
const int maxn=1e5+7;
struct node
{
int l,r,minn;
} tree[maxn];
int arr[maxn];
int n,m,i,j,f,mini,k,T;
//建树
void build(int id,int l,int r)
{
tree[id].l=l;
tree[id].r=r;
if (l==r)
tree[id].minn=arr[l];//如果区间左右端点重合,则不需要继续分区间
else
{
int mid=(l+r)/2;
build(id*2,l,mid);//递归建左子树
build(id*2+1,mid+1,r);//递归建右子树
tree[id].minn=min(tree[id*2].minn,tree[id*2+1].minn);//父节点的最小值即两个分区间的最小值中较小的值
}
}
void update(int id,int pos,int val)
{
if (tree[id].l==pos&&tree[id].r==pos)
{
tree[id].minn=val;
return;
}
if (pos>(tree[id].l+tree[id].r)/2) //右子树
{
update(id*2+1,pos,val);
}
else //左子树
{
update(id*2,pos,val);
}
tree[id].minn=min(tree[id*2].minn,tree[id*2+1].minn);
}
void search(int id,int l,int r)
{
if (l<=tree[id].l&&r>=tree[id].r)//不断更新最小值mini
{
mini=min(mini,tree[id].minn);
}
else
{
int k=(tree[id].l+tree[id].r)/2;
if (l>k)
{
search(2*id+1,l,r);
}
else
{
if (r<=k)
{
search(2*id,l,r);
}
else
{
search(2*id,l,r);
search(2*id+1,l,r);
}
}
}
}
int main()
{
cin>>n;
for (i=1; i<=n; i++)
{
cin>>arr[i];
}
build(1,1,n);
cin>>T;
while(T--)
{
cin>>f;
mini=0x7fffffff;
if (f)
{
int q,w;
cin>>q>>w;
update(1,q,w);
}
else
{
int l,r;
cin>>l>>r;
search(1,l,r);
cout<<mini<<endl;
}
}
return 0;
}
给出自己初学线段树对线段树的理解:
之前的基础树结点基本上只表示一个数,也就是给我们一个一维数组,我们根据维护大小关系或者别的方式维护,最典型的例子是最大堆和最小堆。线段树顾名思义就是维护线段,那么怎么维护呢?
建树:
每一个结点其实是一个结构体,这个结构体里面含有三个数,分别是区间左端点,右端点和这个区间的最小值,也就相当于把区间的最小值都存起来了。如果根结点是【1,n】,我们要求【1,n】中的最小值,就可以求数组前半段【1,mid】和数组后半段【mid+1,n】的最小值,然后取其中较小者作为【1,n】的最小值。如此把每一段区间慢慢分下去,就可以建成一个完整的线段树了。那么比较简单的实现方式就是树论中常见的递归了。
更新树和查找树:
更新树的关键就是找出要修改的树的位置,那么肯定就是找到l=r=pos的地方,用递归查找很容易实现。
查找树的关键就是不断递归,只要【tree[id].l,tree[id].r】区间含在所求区间【l,r】中时,就可以更新【l,r】的最小值,一时更新一时爽,所以一直更新就能更新出最终答案了!
不要忘了这句话 ,很容易遗忘!
总体来说,基础线段树的理解不难,我相信后期线段树的衍生不会让我失望的(微笑面对生活