• 线段树入门


    线段树

    前言

    什么是线段树

    线段树,是一种二叉搜索树。它将一段区间划分为若干单位区间,每一个节点都储存着一个区间。它功能强大,支持区间求和,区间最大值,区间修改,单点修改等操作。

    线段树的思想和分治思想很相像。

    线段树的每一个节点都储存着一段区间[L…R]的信息,其中叶子节点L=R。它的大致思想是:将一段大区间平均地划分成2个小区间,每一个小区间都再平均分成2个更小区间……以此类推,直到每一个区间的L等于R(这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。

    这样一来,每一次修改、查询的时间复杂度都只为O(logN),而空间复杂度一般开4N。

    但是,可以用线段树维护的问题必须满足区间加法,否则是不可能将大问题划分成子问题来解决的。

     证明:空间复杂度开4N

    假设:N=2h   

     

    那么layer[0] = 20,layer[1] = 21+……+layer[h] = 2h

    Sum = 20+21+22+……+2h  =  = 2*2h-1 = 2N-1

    但是这只是特殊情况,大多数情况 N = 2h+M (M<2h

    所有:2h < N < 2h+1     那么    2*2h-1 < Sum < 2*2h+1-1

    那我们就取大的,开2*2h+1-1 = 4*2h-1 < 4N

     

     

    什么是区间加法

    一个问题满足区间加法,仅当对于区间[L,R]的问题的答案可以由[L,M]和[M+1,R]的答案合并得到。经典的区间加法问题有:

    满足区间加法的例子:

    1,区间和        总区间和 = 左区间和+右区间和

    2,最大公因数    总GCD  = GCD(左区间GCD,右区间GCD)

    3,最大值        总最大值 = MAX(左区间MAX,右区间MAX)

    不满足区间加法的例子:

    1,区间众数           总区间总数无法根据左右区间总数求出来

    2,最长递增序列长度    总区间最长递增长度无法直接由左右区间最长递增长度相加而得

    线段树的原理及实现

    注意:如果我没有特别申明的话,这里的询问全部都是区间求和

    线段树主要是把一段大区间平均地划分成两段小区间进行维护,再用小区间的值来更新大区间。这样既能保证正确性,又能使时间保持在log级别(因为这棵线段树是平衡的)。也就是说,一个[L…R]的区间会被划分成[L…(L+R)/2]和[(L+R)/2+1…R]这两个小区间进行维护,直到L=R。

    下图就是一棵[1, 5]的线段树的分解过程(相同颜色的节点在同一层)

     

    可以发现,这棵线段树的最大深度不超过[log2(n−1)]+2(其中[x]表示对x进行下取整)

    a.储存方式

    通常用的都是堆式储存法,即编号为n的节点的左儿子编号为n∗2,右儿子编号为n∗2+1,父节点编号为n/2用位运算优化一下,以上的节点编号就变成:

    左儿子:n << 1 右儿子:n << 1|1 父节点:n >> 1

    通常,每一个线段树上的节点储存的都是这几个变量:区间左边界,区间右边界,区间的答案(这里为区间元素之和)和懒惰标记

    下面是线段树的定义:

    const int maxh = 4*N;     
    struct node      
    {      
        int l;      //左边界     
        int r;      // 右边界     
        int val, lazy;    //节点所维护的区间[l, r]的权值, 懒惰标记      
    }tree[maxh];//N为总节点数      
    int val[maxh]; //原数组  

    b.初始化

    常见的做法是遍历整棵线段树,给每一个节点赋值,注意要递归到线段树的叶节点才结束。

    void pushup(int n)  
    {  
        t[n].val = t[2*n].val+t[2*n+1].val;    //总区间和 = 左区间和+右区间和   
    }  
    void build(int n,int l,int r)  
    {  
        t[n].l = l;     //记录维护的原数组的左端点   
        t[n].r = r;     //记录维护的右端点   
        t[n].lazy = 0;  //标记下懒惰数组   
        if(l==r){       //l==r,表示叶子节点,  
            t[n].val = val[l];    //因为l==r,那么这个节点维护的值就是原数组val[l]的值   
            return;  
        }         
        int mid = (l+r)>>1;  
        build(n*2,l,mid);      //递归建左子树   
        build(n*2+1,mid+1,r);  //递归建右子树  
        pushup(n);             //求该点的权值   
    } 

     

    c.单点修改

    时间复杂度:O(log2(N))

    线段树的高度接近log2(N):所以更新经过的节点数接近log2(N) 

    当我们要把下标为idx的元素修改(加减乘除、赋值运算等),这里我们加上一个值C,可以递归向下找到叶结点,再向上更新,左节点维护的范围是[l, mid],右节点维护的范围是[mid+1, r]。

    如果:idx<=mid,那么val[idx]就由左节点维护,所有走左节点

    如果:mid<idx,那么val[idx]由右节点维护,所有走右节点

    最后直到l==r的时候找到叶子节点,这个节点维护的范围就是[idx, idx]即val[idx]这个元素

    修改这个叶子节点的权值,最后回溯向上修改沿途的节点权值

    void updateOne(int n,int idx,int C)  
    {  
        int l = t[n].l;//左端点   
        int r = t[n].r;//右端点   
        if(l==r)    //l==r,到了叶子节点   
        {  
            t[n].val += C;    //更新权值   
            return;   
        }     
        int mid = (l+r)>>1;  
        if(idx<=mid)     //val[idx]由左节点维护   
            updateOne(n*2,idx,C);  
        else          //val[idx]由右节点维护   
            updateOne(n*2+1,idx,C);  
        pushup(n);    //向上更新   
    }   

       

    模拟更新[2, 5],从n=1开始

    第一轮:节点2:[1, 3]与待更新范围有交集要走,节点3:[4, 5]与待更新范围有交集要走

    第二轮:节点4:[1, 2]有交集要走,节点5:[3, 3]有交集要走,节点[4, 5]被更新范围包括直接更新,lazy+=1不向下走。

    第三轮:节点1:[1, 1]没有交集排除,节点9:[2, 2]被包括更新,节点3:[3, 3]被包括更新

    结束!

     

    时间复杂度:O(log2N)

    定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过2|log2(N-1)| 向下取整子区间。

     

    d.区间修改

    待更新区间为[L, R],左节点维护的区间[l, mid], 右节点维护的区间[mid+1, r]

    L<=mid,左节点维护[l, mid]与[L, R]有交集要走

    mid<R,   右节点维护[mid+1, r]与[L, R]有交集要走

    如果[l, r]被[L, R],直接更新,更新一下lazy标记,不往下走

    递归进行直到更新结束

     

    这里就要引入一样新的神奇的东西——懒惰标记!

    懒惰标记

    标记的含义:本区间已经被更新过了,但是子区间却没有被更新过,但是子结点暂时不更新,使用到时再更新,懒惰标记用于记录更新的信息(如果区间求和只用记录加了多少,而区间加减乘除等多种操作的问题则要记录进行的是哪一种操作,可能记录的信息会复杂一些)

    这里再引入两个很重要的东西:相对标记和绝对标记。

    相对标记&绝对标记

    相对标记指的是可以共存的标记,且打标记的顺序与答案无关,即标记可以叠加。 比如说给一段区间中的所有数字都+a,我们就可以把标记叠加一下,比如上一次打了一个+1的标记,这一次要给这一段区间+2,那么就把+1的标记变成+3。

    绝对标记是指不可以共存的标记,每一次都要先把标记下传,再给当前节点打上新的标记。这些标记不能改变次序,否则会出错。 比如说给一段区间的数字重新赋值,或是给一段区间进行多种操作。

    有了懒惰标记这种神奇的东西,我们区间修改时就可以偷一下懒,先修改当前节点,然后直接把修改信息挂在节点上就可以了!

    如下面这棵线段树,当我们要修改区间[1…4],将元素赋值为1时,我们可以先找到所有的整个区间都要被修改的节点,显然是储存区间[1…3]和[4…4]的这两个节点。我们就可以先把[1…3]的sum改为3((3−1+1)∗1=3,把[4…4]的sum改1((1−1+1)∗1=1 然后给它们打上值为1的懒惰标记,然后就可以了。

    比如下面这颗线段树,当我们修改[1, 4]区间都+1时我们先找到要修改的节点,就是t[2]和t[6],之后修改权值,打上标记

    t[2]={                                             t[6] = {

          l = 1;                                                          l = 4;   

          r = 3;                                                         r = 4;  

          val = 9;                                                      val = 5;

          lazy = 1;                                                    lazy = 1;   

    }                                                           }    

     

     

    这样一来,我们每一次修改区间时只要找到目标区间就可以了,不用再向下递归到叶节点。

    下面是区间[L, R]+C的代码

    void pushdown(int n)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(l==r)//叶子节点没有子结点   
            return;  
        if(t[n].lazy)//懒惰标记不为0才能向下更新   
        {     
            int mid = (l+r)/2;  
            t[2*n].val += t[n].lazy*(mid-l+1);//更新左节点权值   
            t[2*n+1].val += t[n].lazy*(r-mid);//更新右节点权值  
            t[2*n].lazy += t[n].lazy;         //更新左节点标记   
            t[2*n+1].lazy += t[n].lazy;       //更新右节点标记   
            t[n].lazy = 0;                    //清除标记      
        }  
    }  
    void updateRange(int n,int L,int R,int C)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //待更新区间为[L, R],而[l, r]是[L, R]的子集所以更新   
        {  
            t[n].val += (r-l+1)*C;    //更新权值   
            t[n].lazy += C;           //更新标记      
            return;  
        }  
        pushdown(n);    //向下更新   
        int mid = (l+r)>>1;  
        if(L<=mid)      //左节点维护的区间与[L, R]有交集   
            updateRange(2*n,L,R,C);  
        if(R>mid)      //右节点维护的区间与[L, R]有交集   
            updateRange(2*n+1,L,R,C);  
        pushup(n);     //向上更新   
    }  

    e.区间查询

    和区间修改一样的道理,这里是直接返回结果,区间修改是修改权值

    int query(int n,int L,int R)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //被包括直接返回   
            return t[n].val;  
        pushdown(n);        //向下更新   
        int ans = 0;        //保持答案   
        int mid = (l+r)>>1;  
        if(L<=mid)           //查询区间与左节点维护区间有交集   
            ans += query(2*n,L,R);//加上左节点交集区间的答案   
        if(R>mid)  
            ans += query(2*n+1,L,R);//加上右节点交集区间的答案   
        return ans;  
    }  

    模板:

    #include <iostream>  
    using namespace std;  
    const int N = 1e5+10;  
    const int maxh = 4*N;     
    struct node      
    {      
        int l;      //左边界     
        int r;      // 右边界     
        int val, lazy;    //节点所维护的区间[l, r]的权值, 懒惰标记      
    }t[maxh];//N为总节点数      
    int val[maxh]; //原数组   
      
    void pushup(int n)  
    {  
        t[n].val = t[2*n].val+t[2*n+1].val;    //总区间和 = 左区间和+右区间和   
    }  
    void build(int n,int l,int r)  
    {  
        t[n].l = l;     //记录维护的原数组的左端点   
        t[n].r = r;     //记录维护的右端点   
        t[n].lazy = 0;  //标记下懒惰数组   
        if(l==r){       //l==r,表示叶子节点,  
            t[n].val = val[l];    //因为l==r,那么这个节点维护的值就是原数组val[l]的值   
            return;  
        }         
        int mid = (l+r)>>1;  
        build(n*2,l,mid);      //递归建左子树   
        build(n*2+1,mid+1,r);  //递归建左子树  
        pushup(n);             //求该点的权值   
    }  
    void updateOne(int n,int idx,int C)  
    {  
        int l = t[n].l;//左端点   
        int r = t[n].r;//右端点   
        if(l==r)    //l==r,到了叶子节点   
        {  
            t[n].val += C;    //更新权值   
            return;   
        }     
        int mid = (l+r)>>1;  
        if(idx<=mid)     //val[idx]由左节点维护   
            updateOne(n*2,idx,C);  
        else          //val[idx]由右节点维护   
            updateOne(n*2+1,idx,C);  
        pushup(n);    //向上更新   
    }   
    void pushdown(int n)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(l==r)//叶子节点没有子结点   
            return;  
        if(t[n].lazy)//懒惰标记不为0才能向下更新   
        {     
            int mid = (l+r)/2;  
            t[2*n].val += t[n].lazy*(mid-l+1);//更新左节点权值   
            t[2*n+1].val += t[n].lazy*(r-mid);//更新右节点权值  
            t[2*n].lazy += t[n].lazy;         //更新左节点标记   
            t[2*n+1].lazy += t[n].lazy;       //更新右节点标记   
            t[n].lazy = 0;                    //清除标记      
        }  
    }  
    int query(int n,int L,int R)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //被包括直接返回   
            return t[n].val;  
        pushdown(n);        //向下更新   
        int ans = 0;        //保持答案   
        int mid = (l+r)>>1;  
        if(L<=mid)           //查询区间与左节点维护区间有交集   
            ans += query(2*n,L,R);//加上左节点交集区间的答案   
        if(R>mid)  
            ans += query(2*n+1,L,R);//加上右节点交集区间的答案   
        return ans;  
    }  
    void updateRange(int n,int L,int R,int C)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //待更新区间为[L, R],而[l, r]是[L, R]的子集所以更新   
        {  
            t[n].val += (r-l+1)*C;    //更新权值   
            t[n].lazy += C;           //更新标记      
            return;  
        }  
        pushdown(n);    //向下更新   
        int mid = (l+r)>>1;  
        if(L<=mid)      //左节点维护的区间与[L, R]有交集   
            updateRange(2*n,L,R,C);  
        if(R>mid)      //右节点维护的区间与[L, R]有交集   
            updateRange(2*n+1,L,R,C);  
        pushup(n);     //向上更新   
    }  
    int main()  
    {  
        return 0;   
    }  

     

     

    例题

    HDU1166

    https://vjudge.net/problem/HDU-1166

    单点更新+区间查询

    Input

    第一行一个整数T,表示有T组数据。
    每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
    接下来每行有一条命令,命令有4种形式:
    (1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
    (2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
    (3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
    (4)End 表示结束,这条命令在每组数据最后出现;
    每组数据最多有40000条命令

    Output

    对第i组数据,首先输出“Case i:”和回车,
    对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

    #include <iostream>
    using namespace std;
    const int N = 5e5+10;
    const int maxh = 4*N;   
    struct node    
    {    
        int l;      //左边界   
        int r;      // 右边界   
        int val;    //节点所维护的区间[l, r]的权值 
    }t[maxh];//N为总节点数    
    int val[maxh]; //原数组 
    
    void pushup(int n)
    {
        t[n].val = t[2*n].val+t[2*n+1].val;    //总区间和 = 左区间和+右区间和 
    }
    void build(int n,int l,int r)
    {
        t[n].l = l;     //记录维护的原数组的左端点 
        t[n].r = r;     //记录维护的右端点 
        if(l==r){       //l==r,表示叶子节点,
            t[n].val = val[l];    //因为l==r,那么这个节点维护的值就是原数组val[l]的值 
            return;
        }       
        int mid = (l+r)>>1;
        build(n*2,l,mid);      //递归建左子树 
        build(n*2+1,mid+1,r);  //递归建左子树
        pushup(n);             //求该点的权值 
    }
    void updateOne(int n,int idx,int C)
    {
        int l = t[n].l;//左端点 
        int r = t[n].r;//右端点 
        if(l==r)    //l==r,到了叶子节点 
        {
            t[n].val += C;    //更新权值 
            return; 
        }   
        int mid = (l+r)>>1;
        if(idx<=mid)     //val[idx]由左节点维护 
            updateOne(n*2,idx,C);
        else          //val[idx]由右节点维护 
            updateOne(n*2+1,idx,C);
        pushup(n);    //向上更新 
    } 
    int query(int n,int L,int R)
    {
        int l = t[n].l;
        int r = t[n].r;
        if(L<=l && r<=R)    //被包括直接返回 
            return t[n].val;
        int ans = 0;        //保持答案 
        int mid = (l+r)>>1;
        if(L<=mid)          //查询区间与左节点维护区间有交集 
            ans += query(2*n,L,R);//加上左节点交集区间的答案 
        if(R>mid)
            ans += query(2*n+1,L,R);//加上右节点交集区间的答案 
        return ans;
    }
    int main()
    {
        int T;
        scanf("%d", &T);
        for(int cnt = 1;cnt <= T;cnt ++)
        {
            int N;
            char op[10];
            printf("Case %d:
    ", cnt);
            scanf("%d", &N);
            for(int i = 1;i <= N;i ++)
                scanf("%d", &val[i]);    //数组的权值 
            build(1,1,N);                //建树 
            while(scanf("%s", op))      
            {
                if(op[0]=='E')          //END结束     
                    break;
                int i, j;
                scanf("%d%d", &i, &j);
                if(op[0]=='A')          //ADD,第i个营地+j人 
                    updateOne(1,i,j);
                else if(op[0]=='S')     //ADD,第i个营地-j人
                    updateOne(1,i,-j);
                else
                    printf("%d
    ", query(1,i,j));//查询i--j营地一共有多少人 
            }
        }
        return 0; 
    }

     

    HDU1754

    https://vjudge.net/problem/HDU-1754

    Input

    多组测试,EOF结束

    第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。 
    学生ID编号分别从1编到N。 
    第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。 
    接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。 
    当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。 
    当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。 

    Output

    对于每一次询问操作,在一行里面输出最高成绩。

     

    #include <iostream> 
    #include <cmath> 
    using namespace std;  
    const int N = 2e5+10;  
    const int maxh = 4*N;     
    struct node      
    {      
        int l;      //左边界     
        int r;      // 右边界     
        int val;    //节点所维护的区间[l, r]的权值    
    }t[maxh];//N为总节点数      
    int val[maxh]; //原数组   
      
    void pushup(int n)  
    {  
        t[n].val = max(t[2*n].val, t[2*n+1].val);    //总区间和 = 左区间和+右区间和   
    }  
    void build(int n,int l,int r)  
    {  
        t[n].l = l;     //记录维护的原数组的左端点   
        t[n].r = r;     //记录维护的右端点   
        if(l==r){       //l==r,表示叶子节点,  
            t[n].val = val[l];    //因为l==r,那么这个节点维护的值就是原数组val[l]的值   
            return;  
        }         
        int mid = (l+r)>>1;  
        build(n*2,l,mid);      //递归建左子树   
        build(n*2+1,mid+1,r);  //递归建左子树  
        pushup(n);             //求该点的权值   
    }  
    void updateOne(int n,int idx,int C)  
    {  
        int l = t[n].l;//左端点   
        int r = t[n].r;//右端点   
        if(l==r)    //l==r,到了叶子节点   
        {  
            t[n].val = C;    //更新权值   
            return;   
        }     
        int mid = (l+r)>>1;  
        if(idx<=mid)     //val[idx]由左节点维护   
            updateOne(n*2,idx,C);  
        else          //val[idx]由右节点维护   
            updateOne(n*2+1,idx,C);  
        pushup(n);    //向上更新   
    }   
    
    int query(int n,int L,int R)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //被包括直接返回   
            return t[n].val;  
        int ans = 0;        //保持答案   
        int mid = (l+r)>>1;  
        if(L<=mid)           //查询区间与左节点维护区间有交集   
            ans = max(ans, query(2*n,L,R));//加上左节点交集区间的答案   
        if(R>mid)  
            ans = max(ans, query(2*n+1,L,R));//加上右节点交集区间的答案   
        return ans;  
    }  
     
    int main()  
    {  
        int N, M;
        while(scanf("%d%d", &N, &M)!=EOF)
        {
            for(int i = 1;i <= N;i ++)
                scanf("%d", &val[i]);
            build(1,1,N);
            while(M--)
            {
                char C; 
                int A, B;
                getchar();
                scanf("%c%d%d", &C, &A, &B);
                if(C=='Q')
                    printf("%d
    ", query(1,A,B));
                else
                    updateOne(1,A,B);
            }
        }
        return 0;   
    }  

     

    HDU1698

    https://vjudge.net/problem/HDU-1698

    Input

    题目有多组输入:

    第一行输入一个整数T,表示数据组数

    对于每组输入:

    第一行整数N:绳子有多少段,而默认每段绳子价值为1

    第二行整数Q:表示更新操作的次数

    接下来Q行每行输入:X Y Z:表示将[X, Y]段变为价值Z

    Output

    输出绳子的总价值

    #include <iostream>  
    using namespace std;  
    const int N = 1e5+10;  
    const int maxh = 4*N;     
    struct node      
    {      
        int l;      //左边界     
        int r;      // 右边界     
        int val, lazy;    //节点所维护的区间[l, r]的权值, 懒惰标记      
    }t[maxh];//N为总节点数      
      
    void pushup(int n)  
    {  
        t[n].val = t[2*n].val+t[2*n+1].val;    //总区间和 = 左区间和+右区间和   
    }  
    void build(int n,int l,int r)  
    {  
        t[n].l = l;     //记录维护的原数组的左端点   
        t[n].r = r;     //记录维护的右端点   
        t[n].lazy = 0;  //标记下懒惰数组   
        if(l==r){       //l==r,表示叶子节点,  
            t[n].val = 1;    //因为l==r,那么这个节点维护的值就是原数组val[l]的值   
            return;  
        }         
        int mid = (l+r)>>1;  
        build(n*2,l,mid);      //递归建左子树   
        build(n*2+1,mid+1,r);  //递归建左子树  
        pushup(n);             //求该点的权值   
    }  
    void pushdown(int n)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(l==r)//叶子节点没有子结点   
            return;  
        if(t[n].lazy)//懒惰标记不为0才能向下更新   
        {     
            int mid = (l+r)/2;  
            t[2*n].val = t[n].lazy*(mid-l+1);//更新左节点权值   
            t[2*n+1].val = t[n].lazy*(r-mid);//更新右节点权值  
            t[2*n].lazy = t[n].lazy;         //更新左节点标记   
            t[2*n+1].lazy = t[n].lazy;       //更新右节点标记   
            t[n].lazy = 0;                    //清除标记      
        }  
    }  
    void updateRange(int n,int L,int R,int C)  
    {  
        int l = t[n].l;  
        int r = t[n].r;  
        if(L<=l && r<=R)    //待更新区间为[L, R],而[l, r]是[L, R]的子集所以更新   
        {  
            t[n].val = (r-l+1)*C;    //更新权值   
            t[n].lazy = C;           //更新标记      
            return;  
        }  
        pushdown(n);    //向下更新   
        int mid = (l+r)>>1;  
        if(L<=mid)      //左节点维护的区间与[L, R]有交集   
            updateRange(2*n,L,R,C);  
        if(R>mid)      //右节点维护的区间与[L, R]有交集   
            updateRange(2*n+1,L,R,C);  
        pushup(n);     //向上更新   
    }  
    int main()  
    {  
        int T, cnt = 1;
        scanf("%d", &T);
        while(T--)
        {
            int N, Q;
            scanf("%d", &N);
            build(1,1,N);
            scanf("%d", &Q);
            while(Q--)
            {
                int X, Y, Z;
                scanf("%d%d%d", &X, &Y, &Z);
                updateRange(1,X,Y,Z);
            }
            printf("Case %d: The total value of the hook is %d.
    ", cnt++, t[1].val);   
        }
        return 0;   
    }  

     

  • 相关阅读:
    在Raspberry pi4上架设Minecraft1.17
    解决Android-x86在VMware中不能启动GUI的问题
    WMware player 16在超过两台显示器时的多显示器问题
    制动电阻的阻值和功率计算
    拉卡尼休在石块旷野
    在VirtualBox中调整Raspbian分辨率
    如何在VLC播放器中显示毫秒
    EXCEL高亮选中行/列
    Notepad++加强安全性检查,避免被CIA利用
    AutoCAD AutoLisp 脚本编写
  • 原文地址:https://www.cnblogs.com/YukiNote/p/11301577.html
Copyright © 2020-2023  润新知