功能
    在区间上进行信息统计,需注意信息在节点之间传递的属性是什么,根据属性的不同来改变写法

简写
    #define ul u * 2
    #define ur u * 2 + 1
    #define l(u) tr[u].l
    #define r(u) tr[u].r
    #define dat(u) tr[u].dat
    #define lay(u) tr[u].delay

存储( 下标从 1 开始 )
    struct node
    {
        int l, r;                                   //当前节点的左右区间
        int delay;                                  //延迟标记( 只有涉及区间修改时才需 )
        int dat;                                    //信息1
    }tr[N * 4];                                     //节点个数的四倍大小

递归后上捞节点信息( 创建 修改 )
    void pushup( int u )                            //两个子节点递归完成后更新父节点
    {
        tr[u].dat = tr[u * 2].dat + tr[u * 2 + 1].dat;
    }

递归前下传延迟标记( 区间修改 查询 )
    void pushdown( int u )
    {
                                                    //左右子节点分别加上父节点的延迟标记
        tr[u * 2].delay += tr[u].delay;             //和延迟标记对区间所带来的影响
        tr[u * 2].dat += ( tr[u * 2].r - tr[u * 2].l + 1) * tr[u].delay;

        tr[u * 2 + 1].delay += tr[u].delay;
        tr[u * 2 + 1].dat += ( tr[u * 2 + 1].r - tr[u * 2 + 1].l + 1) * tr[u].delay;

        tr[u].delay = 0;                            //节点的延迟标记清零
    }

创建
    void build( int u, int l, int r )               //当前正在创建区间为[l ~ r]的第u个节点
    { 
        tr[u] = { l, r };                           //表明节点的左右区间
        tr[u].delay = 0;                            //和延迟标记
        if( l == r )        
            tr[u].dat = a[l];                       //如果是叶子节点则填充信息
        else
        {                                           //如果不是叶子节点则递归左右节点 
            int mid = l + r >> 1;                   //拆分

            build( u * 2, l, mid );                 //递归左子节点
            build( u * 2 + 1, mid + 1, r );         //递归右子节点

            pushup( u );                            //上捞节点信息
        }       
    }

单点修改
    int modify( int u, int x, int y )               //把编号为 x 的数修改为 y
    {
        if( tr[u].l == x && tr[u].r == x )          //如果是叶子节点则修改信息
            tr[u].dat = y;
        else                                        //如果不是叶子节点则递归左右节点
        {                                      
            int mid = tr[u].l + tr[u].r >> 1;       //拆分

            if( x <= mid )
                modify( u * 2, x, y );              //递归左子节点
            else
                modify( u * 2 + 1, x, y );          //递归右子节点

            pushup( u );                            //上捞节点信息
        }
    }

区间修改
    void modify(int u, int l, int r, int d)         //给区间 [l ~ r] 施加影响 d
    {
        if ( l <= tr[u].l && tr[u].r <= r)          //如果完全覆盖
        {
            tr[u].dat += ( tr[u].r - tr[u].l + 1 ) * d;//更新节点信息
            tr[u].delay += d;                       //给节点打延迟标记
        }
        else                                        //如果不完全覆盖
        {
            pushdown( u );                          //下传延迟标记

            int mid = tr[u].l + tr[u].r >> 1;       //拆分
            if ( l <= mid ) 
                modify( u * 2, l, r, d );           //递归左子节点
            if ( mid < r ) 
                modify( u * 2 + 1, l, r, d );       //递归右子节点

            pushup( u );                            //上捞节点信息
        }
    }


查询
    int query( int u, int l, int r )                //l r 相同则为查询点
    {
        if( l <= tr[u].l && tr[u].r <= r )          //如果完全覆盖     
            return tr[u].dat;                       //返回查询结果备用

        int res = 0;                                //定义查询初值

        pushdown( u );                              //下传延迟标记

        int mid = tr[u].l + tr[u].r >> 1;           //拆分

        if( l <= mid )
            res = ...query( u * 2, l, r );          //递归查询左子节点
        if( mid < r )
            res = ...query(u * 2 + 1, l, r );       //递归查询右子节点

        return res;                                 //返回查询结果
    }