线段树(一)——杨子曰算法

线段树(一)——杨子曰算法

来来来,先问一个问题,给一个长度为n(n<=200000)的序列,然后是m(m<=200000)个询问,对于每个询问x,y,求区间[x,y]的max
噢!!恶心的数据范围,显然是O(m log n)的节奏啊!
没错,我们今天就要用m log n的复杂度来解决这道题


今天我们曰——线段树
首先,它是一棵二叉树,也就是我们用一个数组就可以模拟,即结点i的儿子是i*2和i*2+1,同理i的父亲是i/2
好,先看下线段树长啥样,Look at the 图,就是当n=5时的线段树
这里写图片描述
对的,每一个结点都表示这一个区间,他的左儿子和右儿子就分别是它的左区间和右区间(注意l和r不需要记录在树上,你可以通过结点编号推出来,首先我们已经知道了一号结点的区间是[1,n],那我们就知道2号结点是[1,n/2],3号是[n/2+1,n],同理,我们知道i号结点的区间是[l,r],那么i*2就是[l,mid],i*2+1[mid+1,r],懂否?)
然后你可以在节点上记录一些关于这个结点的信息,比如这道题,我们就可以记录这段区间的max,具体实现:


1.建树(build)
首先递归到叶子节点,那么这个结点上的值,就是对应的a[i],然后回溯,对于每个tree[nod]](在树中编号为nod的结点),它就等于max(tree[nod*2],tree[nod*2+1]),就是左右区间的最大值的最大值

void build(int l,int r,int nod){
    if (l==r){
        tree[nod]=a[l];
        return;
    }//到叶子节点
    int mid=(l+r)/2;
    build(l,mid,nod*2);//造左子树                   
    build(mid+1,r,nod*2+1)//造右子树
    tree[nod]=max(tree[nod*2],tree[nod*2+1]);//回溯,专业人士称之为pushup
} 

2.解决问题(query)
话说这棵子树造好了依然不能解决问题,询问又不会和树上的区间完全吻合,怎么搞搞

int query(int l,int r,int ll,int rr,int nod){
    //l,r为目前所在结点的左右区间
    //ll,rr为要查询的左右区间
    //nod为目前结点的编号
    int mid=(l+r)/2;
}

如果l==ll && r==rr (就是所在区间与查询区间完全重合)直接返回就好了,可如果不是呢?
在这里,我们要分三种情况讨论
1.要查询的区间都在左区间,即(rr<=mid),那么就往左区间走query(l,mid,ll,rr,nod*2)
2.要查询的区间都在左区间,即(ll>mid),那么就往左区间走query(l,mid+1,ll,rr,nod*2+1)
3.最恶心的是要查询的区间一半在左区间,一半在右区间,那我们把查询的区间一分为二,左边往左走,右边往右走,在取个max,也就是返回max(query(l,mid,ll,mid,nod*2),query(mid+1,r,mid+1,rr,nod*2+1))←仔细琢磨一下

int query(int l,int r,int ll,int rr,int nod){
    if (l==ll && r==rr) return tree[nod];
    int mid=(l+r)/2;
    if (rr<=mid) return query(l,mid,ll,rr,nod*2);
    else if (ll>mid) return query(mid+1,r,ll,rr,nod*2+1);
    else return max(query(l,mid,ll,mid,nod*2),query(mid+1,r,mid+1,rr,nod*2+1));
}

好滴,这道题我们就欧了,再来一道几乎一样的
依然是一个长度为n(n<=200000)的序列,依然是m(m<=200000)个询问对于每个询问x,y,输出区间[x,y]的和
呵呵,不就是变成和了吗,做法一样一样的,线段树上区间记录和就好了呀,吧max改成+不久完事了么
Look at the BUILD(从上面复制来的):

void build(int l,int r,int nod){
    if (l==r){
        tree[nod]=a[l];
        return;
    }//到叶子节点
    int mid=(l+r)/2;
    build(l,mid,nod*2);//造左子树                   
    build(mid+1,r,nod*2+1)//造右子树
    tree[nod]=tree[nod*2],tree[nod*2+1]);//回溯,专业人士称之为pushup
} 

再look at the QUERY(依然是复制来的):

int query(int l,int r,int ll,int rr,int nod){
    if (l==ll && r==rr) return tree[nod];
    int mid=(l+r)/2;
    if (rr<=mid) return query(l,mid,ll,rr,nod*2);
    else if (ll>mid) return query(mid+1,r,ll,rr,nod*2+1);
    else return query(l,mid,ll,mid,nod*2)+query(mid+1,r,mid+1,rr,nod*2+1);

OK,完事
推荐一道题:https://blog.csdn.net/henryyang2018/article/details/79688161
有人说,前缀和搞一搞,O(1)就搞定了,为什么要用这种恶心的办法呢?往下看↓


那我再给道题,也为线段树(二)做个预告:
还是一个长度为n(n<=200000)的序列,还是m(m<=200000)个询问,对于每个询问先给出一个k,若k=1则输出x,y输出区间[x,y]的和,若k=2则输入x,y,将第x个数加上y,88
传送门:线段树(二)
于TJQ高层小区902
未经作者允许,严禁转载:https://blog.csdn.net/HenryYang2018/article/details/79836605

猜你喜欢

转载自blog.csdn.net/henryyang2018/article/details/79836605