zoukankan      html  css  js  c++  java
  • 树状数组的简介和线段树基础操作

    树状数组(用于每次只修改一个值的区间查询)

    参考这个,还有这个,图是盗的。。。

    这里A[] 是原数组,C[] 就是树状数组了。

    由演变过程来看,这里运用了二分的思想。设节点编号为x,那么这个节点所管理的区间长度为 2^k (k为x二进制末尾0的个数)。所以 Cn = A(n - 2^k + 1) + ... + An

    关于Lowbit(x) ,这里返回的是 x 用二进制表示时,最低位的1所表示的数,例如x的二进制1100100,返回的是100,就是十进制的4,也就是c[x]所管理的区间长度

    感觉结合图跟模板不难懂

    int n, m, a[MAXN], c[MAXN << 1];
    //计算该点所管理的区间长度 2 ^ k
    int Lowbit(int x)
    {
        return x & (-x);
    }
    //修改
    void change(int x, int num)
    {
        while(x <= n)
        {
            c[x] += num;
            x += Lowbit(x);
        }
    }
    //查询
    int sum(int x)
    {
        int ans = 0;
        while(x > 0)
        {
            ans += c[x];
            x -= Lowbit(x);
        }
    return ans; }

    线段树

    本文参考这篇(By 岩之痕)膜拜大佬

    目录

    1、线段树的简单操作(建树,点修改,查询)

    2、区间修改(区间都加上 num, 区间都变成 num)

    3、非递归线段树(此部分为原文加以排版以及我个人理解,写得真不错)

    上图就是线段树的样子,给个简单的解释,例如这棵树是求区间和的话,那个 [1, 10]算的就是 [1, 10]的和

    1、线段树的简单操作(建树,点修改,查询)

    hdu1166

    //建树,rt代表根,这里不懂的话手动结合上图模拟,从根开始
    void Build(int l, int r, int rt)
    {
        if(l == r)
        {
            scanf("%d", &sum[rt]);
            return ;
        }
        int m = (l + r) >> 1;
        Build(l, m, rt << 1);
        Build(m + 1, r, rt << 1 | 1);
        //更新sum
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }
    //修改,设原数组为 a ,即为 a[x] += num
    void Update(int x, int num, int l, int r, int rt)
    {
        if(l == r)
        {
            sum[rt] += num;
            return ;
        }
        int m = (l + r) >> 1;
        //看 x 在左区间还是右区间
        if(x <= m)
            Update(x, num, l, m, rt << 1);
        else
            Update(x, num, m + 1, r, rt << 1 | 1);
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }
    //查询
    int Query(int L, int R, int l, int r, int rt)
    {
        if(L <= l && r <= R)
            return sum[rt];
        int m = (l + r) >> 1;
        int ans = 0;
        if(L <= m)
            ans += Query(L, R, l, m, rt << 1);//左子区间与[L, R]有重叠
        if(R > m)
            ans += Query(L, R, m + 1, r, rt << 1 | 1);
        return ans;
    }

    上题题解

    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <cmath>
    #include <sstream>
    #include <algorithm>
    #include <set>
    #include <map>
    #include <vector>
    #include <queue>
    #include <iomanip>
    #include <stack>
    
    using namespace std;
    
    typedef long long LL;
    const int INF = 0x3f3f3f3f;
    const int MAXN = 50005;
    const int MOD = 1e9 + 7;
    
    #define MemI(x) memset(x, -1, sizeof(x))
    #define Mem0(x) memset(x, 0, sizeof(x))
    #define MemM(x) memset(x, 0x3f, sizeof(x))
    #define lson l, m, rt << 1
    #define rson m + 1, r, rt << 1 | 1
    int sum[MAXN << 2];
    //建树,rt代表根,这里不懂的话手动结合上图模拟,从根开始
    void Build(int l, int r, int rt)
    {
        if(l == r)
        {
            scanf("%d", &sum[rt]);
            return ;
        }
        int m = (l + r) >> 1;
        Build(l, m, rt << 1);
        Build(m + 1, r, rt << 1 | 1);
        //更新sum
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }
    //修改,设原数组为 a ,即为 a[x] += num
    void Update(int x, int num, int l, int r, int rt)
    {
        if(l == r)
        {
            sum[rt] += num;
            return ;
        }
        int m = (l + r) >> 1;
        //看 x 在左区间还是右区间
        if(x <= m)
            Update(x, num, l, m, rt << 1);
        else
            Update(x, num, m + 1, r, rt << 1 | 1);
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }
    //查询
    int Query(int L, int R, int l, int r, int rt)
    {
        if(L <= l && r <= R)
            return sum[rt];
        int m = (l + r) >> 1;
        int ans = 0;
        if(L <= m)
            ans += Query(L, R, l, m, rt << 1);//左子区间与[L, R]有重叠
        if(R > m)
            ans += Query(L, R, m + 1, r, rt << 1 | 1);
        return ans;
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        for(int cas = 1;cas <= T;++cas)
        {
            printf("Case %d:
    ", cas);
            int n;
            scanf("%d", &n);
            Build(1, n, 1);
            char s[15];
            while(1)
            {
                scanf("%s", s);
                int x, y;
                if(s[0] == 'A')
                {
                    scanf("%d%d", &x, &y);
                    Update(x, y, 1, n, 1);
                }
                else if(s[0] == 'S')
                {
                    scanf("%d%d", &x, &y);
                    Update(x, -y, 1, n, 1);
                }
                    else if(s[0] == 'Q')
                {
                    scanf("%d%d", &x, &y);
                    printf("%d
    ", Query(x, y, 1, n, 1));
                }
                else
                    break;
            }
        }
        return 0;
    }
    View Code

    后面一些线段树的具体题目,链接就不贴了。

    2、区间修改(区间都变成 num,区间都加上num)

    (1)区间都变成num

    我这里定义了一个 book[] 数组作为标记, book[] 也是线段树,在这里存的是修改的值,则代表某一区间的 sum[x] = book[x] * (r - l + 1),这里基本上book[] 就照这个公式存值。如果 book[x] 被标记了,就代表该区间的值已经改变了,同时我们把 sum[x] 的值进行修正,但是!此时该区间的子区间的值没有被修正(对查询操作没有影响,已经得到所要区间的值sum[x])!之后,如果遍历该区间已经被标记了的话,就把该区间的两个子区间进行标记,同时去掉改区间的标记。(延迟下推,直到遍历到该区间才下推)

    void Update(int L, int R, int num, int l, int r, int rt)
    {
        if(L <= l && r <= R)
        {
            sum[rt] = num * (r - l + 1);
            book[rt] = num;
            return ;
        }
        int m = (l + r) >> 1;
        //看这个区间有没有被标记,延迟下推
        PushDown(rt, m - l + 1, r - m);
        if(L <= m)
            Update(L, R, num, lson);
        if(R > m)
            Update(L, R, num, rson);
        PushUp(rt);
    }
    
    void PushDown(int rt, int ln, int rn)
    {
        if(book[rt])
        {
            //标记下推到该区间的两个子区间,同时删除该标记
            book[rt << 1] = book[rt << 1 | 1] = book[rt];
            sum[rt << 1] = ln * book[rt << 1];
            sum[rt << 1 | 1] = rn * book[rt << 1 | 1];
            book[rt] = 0;
        }
    }

    (2)区间都加上num

    这个跟上面类似,上述代码稍微修改一下即可,此时book[] 代表的是增加值

    POJ-3468

    跟上面模板的不同:PushUp() 里面用的是 +=;

    Update()里面用的是 +=;

    Query()里面加了PushDown(),不加会错,因为查询的时候可能会下推标记区间

    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <cmath>
    #include <sstream>
    #include <algorithm>
    #include <set>
    #include <map>
    #include <vector>
    #include <queue>
    #include <iomanip>
    #include <stack>
    
    using namespace std;
    
    typedef long long LL;
    const int INF = 0x3f3f3f3f;
    const int MAXN = 100005;
    const int MOD = 1e9 + 7;
    
    #define MemI(x) memset(x, -1, sizeof(x))
    #define Mem0(x) memset(x, 0, sizeof(x))
    #define MemM(x) memset(x, 0x3f, sizeof(x))
    #define lson l, m, rt << 1
    #define rson m + 1, r, rt << 1 | 1
    
    LL sum[MAXN << 2], book[MAXN << 2];
    void PushUp(int rt)
    {
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }
    
    void PushDown(int rt, int ln, int rn)
    {
        if(book[rt])
        {
            book[rt << 1] += book[rt];
            book[rt << 1 | 1] += book[rt];
            sum[rt << 1] += book[rt] * ln;
            sum[rt << 1 | 1] += book[rt] * rn;
            book[rt] = 0;
        }
    }
    
    void Build(int l, int r, int rt)
    {
        if(l == r)
        {
            scanf("%lld", &sum[rt]);
            return ;
        }
        int m = (l + r) >> 1;
        Build(lson);
        Build(rson);
        PushUp(rt);
    }
    
    void Update(int L, int R, int num, int l, int r, int rt)
    {
        if(L <= l && r <= R)
        {
            sum[rt] += num * (r - l + 1);
            book[rt] += num;
            return ;
        }
        int m = (l + r) >> 1;
        PushDown(rt, m - l + 1, r - m);
        if(L <= m)
            Update(L, R, num, lson);
        if(R > m)
            Update(L, R, num, rson);
        PushUp(rt);
    }
    
    LL Query(int L, int R, int l, int r, int rt)
    {
        if(L <= l && r <= R)
            return sum[rt];
        int m = (l + r) >> 1;
        LL ans = 0;
        PushDown(rt, m - l + 1, r - m);
        if(L <= m)
            ans += Query(L, R, lson);
        if(R > m)
            ans += Query(L, R, rson);
        return ans;
    }
    
    int main()
    {
        Mem0(book);
        Mem0(sum);
        int n, q;
        scanf("%d%d", &n, &q);
        Build(1, n, 1);
        char c[5];
        int l, r, num;
        while(q--)
        {
            scanf("%s%d%d", &c, &l, &r);
            if(c[0] == 'Q')
                printf("%lld
    ", Query(l, r, 1, n, 1));
            else
            {
                scanf("%d", &num);
                Update(l, r, num, 1, n, 1);
            }
        }
        return 0;
    }
    View Code

    这里再贴出参考博客的解释,他的 Add[] 如同我的 book[]


    线段树的区间修改也是将区间分成子区间,但是要加一个标记,称作懒惰标记。

    标记的含义:
    本节点的统计信息已经根据标记更新过了,但是本节点的子节点仍需要进行更新。
    即,如果要给一个区间的所有值都加上1,那么,实际上并没有给这个区间的所有值都加上1,而是打个标记,记下来,这个节点所包含的区间需要加1.打上标记后,要根据标记更新本节点的统计信息,比如,如果本节点维护的是区间和,而本节点包含5个数,那么,打上+1的标记之后,要给本节点维护的和+5。这是向下延迟修改,但是向上显示的信息是修改以后的信息,所以查询的时候可以得到正确的结果。有的标记之间会相互影响,所以比较简单的做法是,每递归到一个区间,首先下推标记(若本节点有标记,就下推标记),然后再打上新的标记,这样仍然每个区间操作的复杂度是O(log2(n))。
     
    标记有相对标记和绝对标记之分:
    相对标记是将区间的所有数+a之类的操作,标记之间可以共存,跟打标记的顺序无关(跟顺序无关才是重点)。
    所以,可以在区间修改的时候不下推标记,留到查询的时候再下推。
          注意:如果区间修改时不下推标记,那么PushUp函数中,必须考虑本节点的标记。
                     而如果所有操作都下推标记,那么PushUp函数可以不考虑本节点的标记,因为本节点的标记一定已经被下推了(也就是对本节点无效了)
    绝对标记是将区间的所有数变成a之类的操作,打标记的顺序直接影响结果,
    所以这种标记在区间修改的时候必须下推旧标记,不然会出错。
     
    注意,有多个标记的时候,标记下推的顺序也很重要,错误的下推顺序可能会导致错误。
     
    之所以要区分两种标记,是因为非递归线段树只能维护相对标记。
    因为非递归线段树是自底向上直接修改分成的每个子区间,所以根本做不到在区间修改的时候下推标记。
    非递归线段树一般不下推标记,而是自下而上求答案的过程中,根据标记更新答案。
    感谢几位网友指出了我的错误。
    我说相对标记在Update时可以不下推,这一点是对的,但是原来的代码是错误的。
    因为原来的代码中,PushUP函数是没有考虑本节点的Add值的,如果Update时下推标记,那么PushUp的时候,节点的Add值一定为零,所以不需要考虑Add。
    但是,如果Update时暂时不下推标记的话,那么PushUp函数就必须考虑本节点的Add值,否则会导致错误。
    为了简便,上面函数中,PushUp函数没有考虑Add标记。所以无论是相对标记还是绝对标记,在更新信息的时候,
    到达的每个节点都必须调用PushDown函数来下推标记,另外,代码中,点修改函数中没有PushDown函数,因为这里假设只有点修改一种操作,
    如果题目中是点修改和区间修改混合的话,那么点修改中也需要PushDown。

     3、非递归线段树(此部分为原文加以排版以及我个人理解。。。)

    非递归的思路很巧妙,思路以及部分代码实现 来自  清华大学 张昆玮 《统计的力量》 ,有兴趣可以去找来看。
    非递归的实现,代码简单(尤其是点修改和区间查询),速度快,建树简单,遍历元素简单。总之能非递归就非递归吧。
    不过,要支持区间修改的话,代码会变得复杂,所以区间修改的时候还是要取舍。有个特例,如果区间修改,但是只需要
    在所有操作结束之后,一次性下推所有标记,然后求结果,这样的话,非递归写起来也是很方便的。
    下面先讲思路,再讲实现。

    (1)点修改:

    非递归的思想总的来说就是自底向上进行各种操作。回忆递归线段树的点修改,首先由根节点1向下递归,找到对应的叶节点,然后,修改叶节点的值,再向上返回,在函数返回的过程中,更新路径上的节点的统计信息。而非递归线段树的思路是,如果可以直接找到叶节点,那么就可以直接从叶节点向上更新,而一个节点找父节点是很容易的,编号除以2再下取整就行了。
    那么,如何可以直接找到叶节点呢?非递归线段树扩充了普通线段树(假设元素数量为n),使得所有非叶结点都有两个子结点且叶子结点都在同一层。
    来观察一下扩充后的性质:
    可以注意到红色和黑色数字的差是固定的,如果事先算出这个差值,就可以直接找到叶节点。
    注意:区分3个概念:原数组下标,线段树中的下标和存储下标。
    原数组下标,是指,需要维护统计信息(比如区间求和)的数组的下标,这里都默认下标从1开始(一般用A数组表示)
    线段树下标,是指,加入线段树中某个位置的下标,比如,原数组中的第一个数,一般会加入到线段树中的第二个位置,
    为什么要这么做,后面会讲。
    存储下标,是指该元素所在的叶节点的编号,即实际存储的位置。
    【在上面的图片中,红色为原数组下标,黑色为存储下标】
    有了这3个概念,下面开始讲区间查询。

    (2)点修改下的区间查询:

    首先,区间的划分没有变,现在关键是如何直接找到被分成的区间。原来是递归查找,判断左右子区间跟[L,R]是否有交点,
    若有交点则向下递归。现在要非递归实现,这就是巧妙之处,见下图,以查询[3,11]为例子。
     
     
    其实,容易发现,紫色部分的变化,跟原来分析线段树的区间分解的时候是一样的规则,图中多的蓝色是什么意思呢?
    首先注意到,蓝色节点刚好在紫色节点的两端。
    回忆一下,原来线段树在区间逐层被替代的过程中,哪些节点被留了下来?最左侧的节点,若为其父节点的右子节点,则留下。
    最右侧的节点,若为其父节点的左子节点则留下。那么对于包裹着紫色的蓝色节点来看,刚好相反。
    比如,以左侧的的蓝色为例,若该节点是其父节点的右子节点,就证明它右侧的那个紫色节点不会留下,会被其父替代,所以没必要在这一步计算,若该节点是其父节点的左子节点,就证明它右侧的那个紫色节点会留在这一层,所以必须在此刻计算,否则以后都不会再计算这个节点了。这样逐层上去,容易发现,对于左侧的蓝色节点来说,只要它是左子节点,那么就要计算对应的右子节点。同理,对于右侧的蓝色节点,只要它是右子节点,就需要计算它对应的左子节点。这个计算一直持续到左右蓝色节点的父亲为同一个的时候,才停止。于是,区间查询,其实就是两个蓝色节点一路向上走,在路径上更新答案。这样,区间修改就变成了两条同时向根走的链,明显复杂度O(log2(n))。并且可以非递归实现。
    至此,区间查询也解决了,可以直接找到所有分解成的区间。
    但是有一个问题,如果要查询[1,5]怎么办?[1]左边可是没地方可以放置蓝色节点了。
    问题的解决办法简单粗暴,原数组的1到n就不存在线段树的1到n了,而是存在线段树的2到n+1,
    而开始要建立一颗有n+2个元素的树,空出第一个和最后一个元素的空间。
    现在来讲如何对线段树进行扩充。(个人笔记:这里的扩充具体是扩充至满二叉树,空余部分0补充)
    再来看这个二叉树,令N=8;注意到,该树可以存8个元素,并且[1..7]是非叶节点,[8..15]是叶节点。
    也就是说,左下角为N的二叉树,可以存N个元素,并且[1..N-1]是非叶节点,[N..2N-1]是叶节点。
    并且,线段树下标+N-1=存储下标 (还记不记得原来对三个下标的定义)
    这时,这个线段树存在两段坐标映射:
    原数组下标+1=线段树下标
    线段树下标+N-1=存储下标 
    联立方程得到:原数组下标+N=存储下标
    于是从原数组下标到存储下标的转换及其简单。
    下一个问题:N怎么确定?
    上面提到了,N的含义之一是,这棵树可以存N个元素,也就是说N必须大于等于n+2
    于是,N的定义,N是大于等于n+2的,某个2的次方。

    (3)区间修改下的区间查询:

    方法之一:如果题目许可,可以直接打上标记,最后一次下推所有标记,然后就可以遍历叶节点来获取信息。
    方法之二:如果题目查询跟修改混在一起,那么,采用标记永久化思想。也就是,不下推标记。
    递归线段树是在查询区间的时候下推标记,使得到达每个子区间的时候,Sum已经是正确值。
    非递归没法这么做,非递归是从下往上,遇到标记就更新答案。
    这题是Add标记,一个区间Add标记表示这个区间所有元素都需要增加Add
    Add含义不变,Add仍然表示本节点的Sum已经更新完毕,但是子节点的Sum仍需要更新.
    现在就是如何在查询的时候根据标记更新答案。
    观察下图:
    左边的蓝色节点从下往上走,在蓝色节点到达[1,4]时,注意到,左边蓝色节点之前计算过的所有节点(即[3,4])都是目前蓝色节点的子节点也就是说,当前蓝色节点的Add是要影响这个节点已经计算过的所有数。多用一个变量来记录这个蓝色节点已经计算过多少个数,根据个数以及当前蓝色节点的Add,来更新最终答案。
    更新完答案之后,再加上[5,8]的答案,同时当前蓝色节点计算过的个数要+4(因为[5,8]里有4个数)
    然后当这个节点到达[1,8]节点时,可以更新[1,8]的Add.
    这里,本来左右蓝色节点相遇之后就不再需要计算了,但是由于有了Add标记,左右蓝色节点的公共祖先上的Add标记会影响目前的所有数,所以还需要一路向上查询到根,沿路根据Add更新答案。

    (4)区间修改:

    这里讲完了查询,再来讲讲修改
    修改的时候,给某个区间的Add加上了C,这个区间的子区间向上查询时,会经过这个节点,也就是会计算这个Add,但是
    如果路径经过这个区间的父节点,就不会计算这个节点的Add,也就会出错。这里其实跟递归线段树一样,改了某个区间的Add
    仍需要向上更新所有包含这个区间的Sum,来保持上面所有节点的正确性。

    (5)非递归实现

    以下以维护数列区间和的线段树为例,演示最基本的非递归线段树代码。

    (0)定义

    #define maxn 100007
    int A[maxn],n,N;//原数组,n为原数组元素个数 ,N为扩充元素个数 
    int Sum[maxn<<2];//区间和 
    int Add[maxn<<2];//懒惰标记 

    (1)建树:

    void Build(int n){
        //计算N的值 
        N=1;while(N < n+2) N <<= 1;
        //更新叶节点 
        for(int i=1;i<=n;++i) Sum[N+i]=A[i];//原数组下标+N=存储下标
        //更新非叶节点 
        for(int i=N-1;i>0;--i){
            //更新所有非叶节点的统计信息 
            Sum[i]=Sum[i<<1]+Sum[i<<1|1];
            //清空所有非叶节点的Add标记 
            Add[i]=0;
        }
    } 

    (2)点修改:

    void Update(int L,int C){
        for(int s=N+L;s;s>>=1){
            Sum[s]+=C;
        }
    } 

    (3)点修改下的区间查询:

    求A[L..R]的和(点修改没有使用Add所以不需要考虑)
    代码非常简洁,也不难理解,
    s和t分别代表之前的论述中的左右蓝色节点,其余的代码根据之前的论述应该很容易看懂了。
    s^t^1 在s和t的父亲相同时值为0,终止循环。
    两个if是判断s和t分别是左子节点还是右子节点,根据需要来计算Sum
    int Query(int L,int R){
        int ANS=0;
        for(int s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1){
            if(~s&1) ANS+=Sum[s^1];
            if( t&1) ANS+=Sum[t^1];
        }
        return ANS;
    } 

    (4)区间修改:

    A[L..R]+=C
    void Update(int L,int R,int C){
        int s,t,Ln=0,Rn=0,x=1;
        //Ln:  s一路走来已经包含了几个数
        //Rn:  t一路走来已经包含了几个数
        //x:   本层每个节点包含几个数
        for(s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1,x<<=1){
            //更新Sum
            Sum[s]+=C*Ln;
            Sum[t]+=C*Rn;
            //处理Add
            if(~s&1) Add[s^1]+=C,Sum[s^1]+=C*x,Ln+=x;
            if( t&1) Add[t^1]+=C,Sum[t^1]+=C*x,Rn+=x;
        }
        //更新上层Sum
        for(;s;s>>=1,t>>=1){
            Sum[s]+=C*Ln;
            Sum[t]+=C*Rn;
        } 
    }

    (5)区间修改下的区间查询:

    求A[L..R]的和
    int Query(int L,int R){
        int s,t,Ln=0,Rn=0,x=1;
        int ANS=0;
        for(s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1,x<<=1){
            //根据标记更新 
            if(Add[s]) ANS+=Add[s]*Ln;
            if(Add[t]) ANS+=Add[t]*Rn;
            //常规求和 
            if(~s&1) ANS+=Sum[s^1],Ln+=x;
            if( t&1) ANS+=Sum[t^1],Rn+=x; 
        }
        //处理上层标记
        for(;s;s>>=1,t>>=1){
            ANS+=Add[s]*Ln;
            ANS+=Add[t]*Rn;
        }
        return ANS;
    }

    以下为个人做题练手

    hdu1754

    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <cmath>
    #include <queue>
    #include <iomanip>
    #include <stack>
    #include <algorithm>
    #include <vector>
    #include <functional>
    
    using namespace std;
    
    typedef long long LL;
    #define FIL(Array, len, num) fill(Array, Array + len, num)
    #define lson l, m, rt << 1
    #define rson m + 1, r, rt << 1 | 1
    const double PI = 3.1415926;
    const double E = 2.1718281828;
    const int MAXN = 200005;
    const int INF = 0x3f3f3f3f;
    const int MOD = 1e9 + 7;
    
    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 - '0';ch = getchar();}
        return x * f;
    }
    
    //推算都是由子叶开始推向根
    //cnt 线段树下标
    int n, cnt;
    int sum[MAXN << 2];
    void Build()
    {
        cnt = 1;
        //根据原数组下标  + cnt = 存储下标
        while(cnt < n + 2)
            cnt <<= 1;
        for(int i = 1;i <= n;++i)
            scanf("%d", &sum[cnt + i]);
        for(int i = cnt - 1;i > 0;--i)
            sum[i] = max(sum[i << 1], sum[i << 1 | 1]);
    }
    
    void Update(int L, int C)
    {
        sum[cnt + L] = C;
        for(int i = (cnt + L) >> 1;i > 0;i >>= 1)
            sum[i] = max(sum[i << 1], sum[i << 1 | 1]);
    }
    
    int Query(int L, int R)
    {
        int ret = 0;
        //想过把左右蓝色边界去掉,但是是不可行的
        //例如取得区间是[3, 6],跟取区间[3, 4]的一样的结束条件
        for(int l = cnt + L - 1, r = cnt + R + 1;l ^ r ^ 1;l >>= 1, r >>= 1)
        {
            if(~ l & 1) ret = max(ret, sum[l ^ 1]);
            if(r & 1) ret = max(ret, sum[r ^ 1]);
        }
        return ret;
    }
    
    int main()
    {
        int m;
        while(scanf("%d%d", &n, &m) != EOF)
        {
            Build();
            while(m--)
            {
                char c[10];
                int a, b;
                scanf("%s%d%d", &c, &a, &b);
                if(!strcmp(c, "Q"))
                    printf("%d
    ", Query(a, b));
                else
                    Update(a, b);
            }
        }
        return 0;
    }
    View Code
    现在所有的不幸都是以前不努力造成的。。。
  • 相关阅读:
    wordpress 自己制作子主题 child theme
    wordpress 主题开发
    python的曲线平滑工具,及python画一条线中包含不同粗细不同颜色的画线方法
    openfire 使用已有的数据库作为用户认证数据库 Custom Database Integration Guide
    How to store scaling parameters for later use
    在android上跑 keras 或 tensorflow 模型
    Win10更新搜狗输入法后重启输入密码蓝屏
    Audition CC2019 MME设备内部错误怎么解决!
    数据库优化解决方案
    微信小程序文本如何换行
  • 原文地址:https://www.cnblogs.com/shuizhidao/p/9581188.html
Copyright © 2011-2022 走看看