java,线段树

线段树:

  你可以理解成:线段组成的树,很多人问我,线段树到底有何用处,其实这个问题,你可以自己去刷题,然后总结出检验。

线段的具体理解,我看到一篇很好的博客,我就不展开了。博客地址:https://blog.csdn.net/iwts_24/article/details/81484561

基础题目:

hdu1166敌兵布阵

如果我们没有学过线段树,我们肯定是用模拟+暴力的方法。

模拟+暴力的方法代码:

package Combat.com;

import java.math.BigInteger;
import java.util.Scanner;

public class Main
{
    static final int MAX = 50005;
    static int camp[] = new int[MAX];
    public static void main(String []args)
    {
        Scanner cin = new Scanner(System.in);
        int T = cin.nextInt();
        for(int i = 1; i <= T; i++)
        {
            int N = cin.nextInt();
            for(int j = 1; j <= N; j++)
            {
                camp[j] = cin.nextInt();
            }
            System.out.println("Case " + i +":" );
            search(N);
        }
    }
    static void search(int N)
    {
        Scanner cin = new Scanner(System.in);
        while(true)
        {
            String input = cin.next();
            if(input.equals("End"))
            {
                return;
            }
            int i = cin.nextInt();
            int j = cin.nextInt();
            if(input.equals("Add"))
            {
                camp[i] += j;
            }
            else if(input.equals("Sub"))
            {
                camp[i] -= j;
            }
            else
            {
                int output = 0;
                for(int k = i; k <= j; k++)
                {
                    output += camp[k];
                }
                System.out.println(output);
            }
        }
    }
}

上面的代码随着输入的用例越多,是极易超时的。

正确的方法是:线段树中的:单点修改+区间查询。

代码实现如下:

package Combat.com;

import java.util.Scanner;

public class Main
{
    static final int MAX = 50005;
    static int camp[] = new int[MAX*4];
    static Scanner cin = new Scanner(System.in);
    public static void main(String []args)
    {
        int T = cin.nextInt();
        for(int i = 1; i <= T; i++)
        {
            int N = cin.nextInt();
            buildBinaryTree(1,N,1);
            System.out.println("Case " + i + ":");
            search(N);
        }
    }
    static void search(int N)
    {
        while(true)
        {
            String input = cin.next();
            if(input.equals("End"))
            {
                return;
            }
            int i = cin.nextInt();
            int j = cin.nextInt();
            if(input.equals("Add"))
            {
                update(1,i,j,1,N);
            }
            else if(input.equals("Sub"))
            {
                update(1,i,-j,1,N);
            }
            else
            {
                System.out.println(query(1,i,j,1,N));
            }
        }
    }
    static int query(int node,int l,int r,int L,int R)//不用分开考虑是否跨区间。下面的代码已经考虑在内了。
    {
        if(l <= L && R <= r)
        {
            return camp[node];
        }
        int mid = (L+R)/2;
        int sum = 0;
        if(l <= mid)
        {
            sum += query(node*2,l,r,L,mid);
        }
        if(r > mid)
        {
            sum += query(node*2+1,l,r,mid+1,R);
        }
        return sum;
    }
    static void update(int node,int pos,int num,int L,int R)
    {
        if(L == R)
        {
            camp[node] += num;
            return;
        }
        int mid = (L+R)/2;
        if(pos <= mid)
        {
            update(node*2,pos,num,L,mid);
        }
        else
        {
            update(node*2+1,pos,num,mid+1,R);
        }
        camp[node] = camp[node*2]+camp[node*2+1];//这句话极其重要
    }
    static void buildBinaryTree(int L,int R,int node)
    {
        if(L == R)
        {
            camp[node] = cin.nextInt();
            return;
        }
        int mid = (L+R)/2;
        buildBinaryTree(L,mid,node*2);
        buildBinaryTree(mid+1,R,node*2+1);
        camp[node] = camp[node*2]+camp[node*2+1];
    }
}

线段树的原理与模板

2018年08月08日 20:10:34 iwts_poi 阅读数:405

版权声明:转载请注明原址,有错误欢迎指出 https://blog.csdn.net/iwts_24/article/details/81484561

如果会树状数组的同学应该就很容易理解线段树了,在一定程度上,两者是有一点类似的。首先,了解一下我们为什么要使用线段树,以及线段树的主要作用。

区间求和问题-医院卖药

假设有一家医院,医院有卖药的地方,不同的药品有不同的数量。每次开药、进药都要在计算机里面记录数量变化,这样方便医院的管理。那么我们该如何实现这样的程序?当然,药品数量的储存用数组是比较合适的(真实的项目当然要用数据库)

int a[8] = {1,2,3,4,5,6,7,8};

现在,给出指令,第1号到第7号,这7个药品的数量全部加8,那么代码实现就是标准的for循环了:

  1. for(int i = 0;i < 7;i++){

  2.  

    a[i] += 8;

  3.  

    }

同样,如果进药,仍然循环即可。所以对于区间数量的变化,时间复杂度为O(n)

那么现在我们需要算所有药品的数量,最简单的方法仍然是for循环:

  1. int sum = 0;

  2.  

    for(int i = 0;i < 8;i++){

  3.  

    sum += a[i];

  4.  

    }

这样,就算我们提高要求,求出区间[a,b]之间的和,仍然遍历即可,算法复杂度为O(b-a)

看起来,我们对于区间的操作,无论是求和还是数据修改,都能在线性的时间段内完成。但是这样的时间复杂度其实并不好。一般来说,比赛中数据量1000000就差不多了,但是我们的操作数量可是非常多的。这样的数据范围:有m个数据0<m<=1000000,有n次操作0<n<10000。那么这样,就算是线性的时间复杂度仍然有超时的可能。

所以,我们就需要用线段树这样的数据结构来储存数据,从而降低操作的时间复杂度。

什么是线段树

这里提一下完全二叉树。完全二叉树是叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边。

那么线段树就是完全二叉树,一定条件下成为满二叉树。

线段树的主要思想是二分,也就是通过二分的方法来查找节点。首先看一下线段树的图的表示(这里用了百度百科的图)

标题

实际上,这是数据为8的线段树。蓝色的标记是二叉树的节点编号,最下面的红色的框体框住的8个叶子就是实际上数据存放的位置。而节点内部写的区间,就是指数据存放区间的和。例如2号节点,里面区间是[1,4]也就是1号到4号之间的数据的和。

线段树的操作-建树

基本的线段树就如上图所示,那么我们首先应该基于一段数据进行建树操作。可以看出,线段树是非常耗费空间的。所以我们不管是用结构体还是数组,在表示线段树的时候都要在数据量n的情况下多开更大的空间。一般都是开四倍。所以基本的声明与初始化如下:

  1. #define MAX 2333

  2.  

  3.  

    int tree[4*MAX];

  4.  

  5.  

    void init(){

  6.  

    memset(tree,0,sizeof(tree));

  7.  

    }

然后就是建树了,我们需要将数组变成一个树。代码如下:

  1. void build(int node,int l,int r){

  2.  

    if(l == r){ // 到达叶子节点,赋值

  3.  

    cin >> tree[node];

  4.  

    return;

  5.  

    }

  6.  

    int mid = (l+r)/2;

  7.  

    build(node*2,l,mid); // 进入子树开始递归

  8.  

    build(node*2+1,mid+1,r);

  9.  

    tree[node] = tree[node*2] + tree[node*2 + 1]; // 回溯

  10.  

    }

这样的建树,是一个递归的过程。l与r分别表示区间,结合上面的图,当l==r的时候说明递归已经遍历到叶子节点了,而这个节点node也是二叉树的节点编号。对应了数组的下标。所以进行赋值。然后直接return进行回溯。那么在正常递归的时候,我们需要利用二叉树的性质,即对于node编号的节点而言,左子树编号为2*node,右子树为2*node+1。同样,由于二分的性质,利用mid = (l+r)/2,就可以获取下一个子树的区间范围。

在回溯的时候,是从树的最下层开始向最上层回溯,那么同样利用二叉树的性质,我们可以轻松将子树的数据加到父节点上。这样,当函数完成的时候,我们就可以利用数组来构建了一个线段树。

线段树的操作-单点修改

线段树并不必须要进行区间的操作,如果是对单点进行操作,完全可以用更快的方法来实现。而对于单点修改而言,其实相比区间修改的代码要简单很多(因为lazy数组的存在),所以能用针对单点的修改最好不要用区间修改。单点更新非常类似二分查找,通过递归找到更新点的位置,在回溯的时候更新所有节点的值。代码:

  1. // 单点更新,n为更新值,index为更新点,lr为更新范围

  2.  

    void update(int n,int index,int l,int r,int node){

  3.  

    if(l == r) {

  4.  

    tree[node] += n; // 更新方式,可以自由改动

  5.  

    return;

  6.  

    }

  7.  

    int mid = (l+r) / 2;

  8.  

    // push_down(node,mid-l+1,r-mid); 若既有点更新又有区间更新,需要这句话

  9.  

    if(index <= mid){

  10.  

    update(n,index,l,mid,node*2);

  11.  

    }else{

  12.  

    update(n,index,mid+1,r,node*2+1);

  13.  

    }

  14.  

    tree[node] = tree[node*2] + tree[node*2 + 1]; // 回溯过程,需要将线段树上层数据更新

  15.  

    }

l与r就是指当前节点的区间范围,刚开始肯定是填1,8了,因为线段树的最上边节点代表的范围(参照上图的白色区间字体)就是1,8。那么,如果l==r,就说明到达了叶子节点,当然就是需要更新的节点了。如何进入递归?就跟二分一样,index与mid进行判断,看是走左子树还是右子树。最后一定不要忘记回溯,因为叶子节点更新后,上层节点的值也需要更新,这样才算维护了线段树。

里面有一行注释的代码,这里需要把后面的懒惰标记、区间操作学完才能理解。现在就当不存在吧。

懒惰标记

在进行区间操作之前,要首先理解线段树的懒惰标记,试想,我们在操作的时候有可能有这样的操作。首先进行区间修改,修改了800次,然后再进行一次查询。这样,如果我们每次都将整个线段树的数据进行更新,实际上是非常慢的,如果我们能用一段空间,来记录修改数据,只有在使用的时候,一次性更新,就非常的方便。

所以这也是懒惰标记的作用。可以先对修改的数据进行储存,只有在使用的时候才更新线段树。那么,理论上我们应该建一个跟线段树同样大小的数组,称为懒惰数组,表示了每个节点的懒惰标记。有这样的操作:

1.修改数据的时候,每次递归到某节点,修改数据以后将数据的变化添加到数组中。

2.当使用到这个节点的时候,发现对应的懒惰标记存在,那么就应该更新该节点,以及以下的所有节点的数据,方便使用。

总之,就是不使用的时候就一直在积累,在使用的时候再统一更新。

那么懒惰数组的更新非常简单,对线段树更新的时候就可以添加到懒惰标记,但是在使用的时候,我们需要用一个函数来完成懒惰标记的下传操作,也就是更新积累的值。代码:

  1. void push_down(int node,int l,int r){

  2.  

    if(lz[node]){

  3.  

    int mid = (l+r) / 2;

  4.  

    lz[node*2] += lz[node];

  5.  

    lz[node*2 + 1] += lz[node];

  6.  

    // 注意线段树的数据更新方式要一致

  7.  

    tree[node*2] += 1LL*(mid - l + 1)*lz[node];

  8.  

    tree[node*2 + 1] += 1LL*(r - mid)*lz[node];

  9.  

    lz[node] = 0;

  10.  

    }

  11.  

    }

lz数组,即lazy,就是懒惰标记数组。可以看出,当lz[node]存在值的时候,就说明现在我在使用这个节点,而这个节点以及其下的节点需要更新了,所以就利用二叉树的性质向下传递更新数据,同时更新线段树中的数据。最终,要将该节点的懒惰标记清零。

注意,下推的时候不是一直更新到叶子节点,而是只更新当前节点以及2个子树,因为实际操作的时候,只要碰到对某节点的操作就要调用push_down()函数,所以每次只用下推一层即可。

push_down()函数的使用需要在下面的区间操作中添加。

线段树的操作-区间更新

单点更新类似二分查找,更新的时候对经过的路径进行操作就可以了。但是区间更新需要考虑整个区间。线段树除了叶子节点,都表示了一段区间的值,那么就要配合懒惰标记在整个区间上进行操作。先看代码:

  1. // 区间更新,lr为更新范围,LR为线段树范围,add为更新值

  2.  

    void update_range(int node,int l,int r,int L,int R,int add){

  3.  

    if(l <= L && r >= R){

  4.  

    lz[node] += 1LL*add;

  5.  

    tree[node] += 1LL*(R - L + 1)*add; // 更新方式

  6.  

    return;

  7.  

    }

  8.  

    push_down(node,L,R);

  9.  

    int mid = (L+R) / 2;

  10.  

    if(mid >= l) update_range(node*2,l,r,L,mid,add);

  11.  

    if(mid < r) update_range(node*2 + 1,l,r,mid+1,R,add);

  12.  

    tree[node] = tree[node*2] + tree[node*2 + 1];

  13.  

    }

仍然以[1,8]区间的线段树为例,修改[1,6]的区间,使区间内的数据全部加1。那么我们可以在线段树图的基础上手动走一遍这个代码,来理解线段树的区间操作以及与懒惰标记的配合。

这个是一个线段树,现在我们需要对[1,6]范围内进行加一,那么就应该这样:

update_range(1,1,6,1,8,1); // 从结点1,即最上边开始

我们手动走一遍上述代码,首先,if判断不成立。这个if判断主要判定的是区间,我们每次递归进入函数,操作区间与当前结点表示区间总共有2种可能:

1.操作区间完全与结点表示区间重合。此时,说明我们已经可以对该结点进行操作了。

2.操作区间在结点表示区间内。此时,说明现在的结点表示空间太大了,需要向下寻找更合适的区间。

3.操作区间比结点表示区间大,只有向下推的时候才能出现这样的情况,跟第一种可能的结果一样,对结点进行操作就行了。

4.操作区间与结点表去区间只有一部分重合。只有向下推的时候才能出现这样的情况,跟第二种可能的结果一样,下推。

5.操作区间与结点表示区间完全不重合。无操作,等待程序自己结束。

其他可能是不存在的,例如操作区间大于结点表示区间。因为操作区间最大不可能超过线段树的区间,所以越分越小的情况下,最少就是跟结点表示区间一致。

所以,这个if,就是判断操作区间与结点表示区间满足什么条件。接着上例,[1,6]与[1,8]相比,明显是第二种情况,所以需要向下推移。此时,由于开始接触下面的结点了,所以需要调用push_down,进行懒惰标记向下推移,保证接触的是数据已经修改过的结点。然后当然是区分左子树右子树了,2种可能,进入左子树或右子树。

首先是进入左子树的情况,此时操作区间仍然是[1,6],而结点表示区间是[1,4]出现了情况3。此时,说明[1,4]区间的所有数据都要变化。这也进入了if判断之中。不要担心[5,6]区间,这一部分已经递归进入右子树了。此时,在该结点处更新懒惰标记与数据信息。注意这句代码:

tree[node] += 1LL*(R - L + 1)*add;

[1,4]结点存放的是1-4的数据,所以每个数据+1,这个结点就要+4。同时,我们已经标记懒惰标记了,所以直接return。下面的结点全部不更新,等到需要的时候在下推就行了。至此,左子树的递归就完成了。return后回溯,此时进入右子树的递归。

进入右子树,此时操作区间仍然是[1,6],而结点表示区间则变成了[5,8],是情况4,所以仍然下推。这时候,左子树变成[5,6],右子树变成[7,8]。左子树变成情况1了,那么就进行更新。右子树已经完全脱离区间,所以不用管,等他自己递归完成后没有任何操作地结束吧。

注意,程序的最后还有回溯这个过程。确实,我们的操作因为懒惰数组是不再向下传递了。但是已经更新的结点需要在回溯的时候向上更新。由于刚开始,我们就是从结点1开始的,所以回溯一定能将更新结点之前的结点全部更新。

最后得到的图如下:

红字就是更新的结点,而旁边标记的红色的1就是懒惰标记。下次调用有懒惰标记的结点就会导致更新与向下传递懒惰标记。这样,我们就完成了区间更新的操作。

所以,这里有最最最重要的一点:由于懒惰标记,所以很多数据是没有变化的。如果在一次区间更新后就需要调用真实数据,那么应该先向下更新全部数据后再调用。否则得到的数据是未更新的错误数据。这样,我们也能理解单点操作的那一行注释的代码了,其实就是下推懒惰标记。因为只要使用了区间操作,就有可能有懒惰标记产生。所以需要使用push_down()来下推。

线段树的操作-区间查找

区间查找的原理跟区间更新基本一样,也是看结点表示的数据范围有不同的操作。同样,在到某个结点的时候一定要调用push_down()。不同点在于跟数据操作无关,但是需要一个sum来储存收集到的区间数据,同时最后return。这样在递归完成以后最后返回的就是区间和了。理解区间更新后,区间查找的代码就非常容易了:

  1. // 区间查找

  2.  

    LL query_range(int node,int L,int R,int l,int r){

  3.  

    if(l <= L && r >= R) return tree[node];

  4.  

    push_down(node,L,R);

  5.  

    int mid = (L+R) / 2;

  6.  

    LL sum = 0;

  7.  

    if(mid >= l) sum += query_range(node*2,L,mid,l,r);

  8.  

    if(mid < r) sum += query_range(node*2 + 1,mid+1,R,l,r);

  9.  

    return sum;

  10.  

    }

脱离lazy数组

lazy数组的使用在很大程度将降低了解决问题所耗费的时间,但是也增加了对模板的修改难度。诚然,lazy很实用,但是在一些问题的构造上并不容易修改。我们平常的区间修改,整个区间的数值变化是统一的,所以我们能够在数学上提前好多个结点先算出来更改情况。但是有很多问题并不是这样的。

例如:hdu4027。11年上海网络赛,要求区间内对每个节点的数值取其二次根。那么再考虑lazy数组就是徒生烦恼。如果我们抛弃lazy数组,直接每次都更新到叶子结点,同时考虑剪枝,速度也并不慢(500ms)。所以,在区间操作不平衡,同时可以剪枝的情况下,完全可以抛弃lazy数组,从而修改为:

  1. boolean cleck(int node,int l,int r){

  2.  

    // 剪枝条件

  3.  

    }

  4.  

  5.  

    void update_range(int node, int l, int r, int L, int R) {

  6.  

    if (L == R) {

  7.  

    tree[node] = 1; // 更新方式

  8.  

    return;

  9.  

    }

  10.  

    int mid = (L + R) / 2;

  11.  

    if (mid >= l && cleck(node*2,L,mid)) update_range(node * 2, l, r, L, mid);

  12.  

    if (mid < r && cleck(node*2+1,mid+1,R)) update_range(node * 2 + 1, l, r, mid + 1, R);

  13.  

    tree[node] = tree[node * 2] + tree[node * 2 + 1];

  14.  

    }

抛弃lazy数组,l与r是区间范围,每次递归修改的是L与R,表示的是实际区间范围。当L == R的时候就到达叶子节点了。每次递归的时候添加剪枝条件,满足了才能继续递归。所有非叶子结点的更新全部在回溯的过程中进行。

最后

以上就是线段树的基本操作了。对于单点的操作,其实用的不多,同时也能用区间操作代替(例如l与r,l==r的情况就是单点操作了)。难点在于懒惰标记的问题,理解以后其实也并不太难。推荐刷一下kuangbin的线段树专题,可以加深对于线段树的理解。

[kuangbin带你飞]专题七 线段树

下面是模板的代码,当然是在main函数里面调用了。

  1. #include<iostream>

  2.  

    #include<string>

  3.  

    #define LL long long

  4.  

    #define MAX 1001

  5.  

  6.  

    using namespace std;

  7.  

  8.  

    int tree[MAX]; // 线段树

  9.  

    int lz[MAX]; // 延迟标记

  10.  

  11.  

    void init(){

  12.  

    memset(tree,0,sizeof(tree));

  13.  

    memset(lz,0,sizeof(lz));

  14.  

    }

  15.  

  16.  

    // 创建线段树

  17.  

    void build(int node,int l,int r){

  18.  

    if(l == r){

  19.  

    cin >> tree[node];

  20.  

    return;

  21.  

    }

  22.  

    int mid = (l+r)/2;

  23.  

    build(node*2,l,mid);

  24.  

    build(node*2+1,mid+1,r);

  25.  

    tree[node] = tree[node*2] + tree[node*2 + 1];

  26.  

    }

  27.  

  28.  

    // 单点更新,n为更新值,index为更新点,lr为更新范围

  29.  

    void update(int n,int index,int l,int r,int node){

  30.  

    if(l == r) {

  31.  

    tree[node] = n; // 更新方式,可以自由改动

  32.  

    return;

  33.  

    }

  34.  

    int mid = (l+r) / 2;

  35.  

    // push_down(node,mid-l+1,r-mid); 若既有点更新又有区间更新,需要这句话

  36.  

    if(index <= mid){

  37.  

    update(n,index,l,mid,node*2);

  38.  

    }else{

  39.  

    update(n,index,mid+1,r,node*2+1);

  40.  

    }

  41.  

    tree[node] = tree[node*2] + tree[node*2 + 1];

  42.  

    }

  43.  

  44.  

    void push_down(int node,int l,int r){

  45.  

    if(lz[node]){

  46.  

    int mid = (l+r) / 2;

  47.  

    lz[node*2] += lz[node];

  48.  

    lz[node*2 + 1] += lz[node];

  49.  

    tree[node*2] += 1LL*(mid - l + 1)*lz[node];

  50.  

    tree[node*2 + 1] += 1LL*(r - mid)*lz[node];

  51.  

    lz[node] = 0;

  52.  

    }

  53.  

    }

  54.  

  55.  

    // 区间更新,lr为更新范围,LR为线段树范围,add为更新值

  56.  

    void update_range(int node,int l,int r,int L,int R,int add){

  57.  

    if(l <= L && r >= R){

  58.  

    lz[node] += 1LL*add;

  59.  

    tree[node] += 1LL*(R - L + 1)*add; // 更新方式

  60.  

    return;

  61.  

    }

  62.  

    push_down(node,L,R);

  63.  

    int mid = (L+R) / 2;

  64.  

    if(mid >= l) update_range(node*2,l,r,L,mid,add);

  65.  

    if(mid < r) update_range(node*2 + 1,l,r,mid+1,R,add);

  66.  

    tree[node] = tree[node*2] + tree[node*2 + 1];

  67.  

    }

  68.  

  69.  

    // 区间查找

  70.  

    LL query_range(int node,int L,int R,int l,int r){

  71.  

    if(l <= L && r >= R) return tree[node];

  72.  

    push_down(node,L,R);

  73.  

    int mid = (L+R) / 2;

  74.  

    LL sum = 0;

  75.  

    if(mid >= l) sum += query_range(node*2,L,mid,l,r);

  76.  

    if(mid < r) sum += query_range(node*2 + 1,mid+1,R,l,r);

  77.  

    return sum;

  78.  

    }

  79.  

  80.  

    int main() {

  81.  

    init();

  82.  

    build(1,1,8);

  83.  

  84.  

    system("pause");

  85.  

    return 0;

  86.  

原文地址:https://www.cnblogs.com/674001396long/p/10800969.html

时间: 2024-10-07 06:48:08

java,线段树的相关文章

[java线段树]2015上海邀请赛 D Doom

题意:n个数 m个询问 每个询问[l, r]的和, 再把[l, r]之间所有的数变为平方(模为9223372034707292160LL) 很明显的线段树 看到这个模(LLONG_MAX为9223372036854775807) 很明显平方时会爆LL 很容易发现所有数平方模了几次之后值就不再改变了 而且这个“几次”相当小 因此直接暴力搞就好了 public static void main(String[] args) { Scanner in = new Scanner(System.in);

Java线段树

线段树不是完全二叉树,是平衡二叉树 堆也是平衡二叉树 堆满二叉树: h层,一共有2^h-1个节点(大约是2^h) 最后一层(h-1层)有2^(h-1)个节点 最后一层的节点数大致等于前面所有层节点之和 如果区间有n个元素,数组表示需要4n的空间 不考虑添加元素,使用4n的静态空间即可 原文地址:https://www.cnblogs.com/sunliyuan/p/10720167.html

线段树的 JAVA 和 JS 实现一把梭

理解一个数据结构,我们应该首先明白该数据结构的作用与应用场景,尔后理清其逻辑结构,基于逻辑结构考虑如何在计算机上进行物理存储,最后对以上进行代码实现. 我们按上述思考顺序来实现一次线段树. 作用及应用场景 我们考虑一个场景,我们有一个长度为 n 的数组,我们需要经常进行两种操作: 1. 计算某个区间内数组元素的和. 2. 修改数组中的某个元素. 我们考虑时间复杂度,明显的,计算区间和的时间复杂度为 O(N) ,而对于一个数组来说,修改一个元素的时间复杂度为 O(1) . 我们想要优化一下计算区间

java 操作格子问题(线段树)

很久之前做过线段树的问题(操作格子),时间长了之后再次接触到,发现当初理解的不是很透彻,然后代码冗长,再遇到的时候发现自己甚至不能独立地完成这个问题. 所以算法这个东西啊, 第一,是要经常练习(我个人认为-每一个程序员都不应该不擅长算法-从今天开始,要常写博客!). 第二,是一定要理解透彻,理解透彻并不是说到网上找到了解答,然后自己照着能够运行出来,这样是不够的!甚至不是说你看完了一个算法之后,完全不看他的解答,然后你自己写出来,这样也是不够的! 先贴题目: 问题描述 有n个格子,从左到右放成一

HDU 1754 I Hate It(线段树之单点更新,区间最值)

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 70863    Accepted Submission(s): 27424 Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感.不管你喜不喜欢,现在需要你做的是,就是按照老师的

HDU1832 二维线段树求最值(模板)

Luck and Love Time Limit: 10000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 50 Accepted Submission(s): 20   Problem Description 世界上上最远的距离不是相隔天涯海角而是我在你面前可你却不知道我爱你                ―― 张小娴 前段日子,枫冰叶子给Wiskey做了个征婚启事,聘

hdu 1754 I Hate It 线段树(插点问点)

线段树入门题,年前做过线段树类型的题,不过是用树状数组或者rmq做的,没用线段树(其实是不会), 看了这张图原理应该就明白了, http://blog.csdn.net/x314542916/article/details/7837276(图片来源) I Hate It Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 60381 Accept

HDU 1689 Just a Hook 线段树区间更新求和

点击打开链接 Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 18894    Accepted Submission(s): 9483 Problem Description In the game of DotA, Pudge's meat hook is actually the most horrible

hdu 1698 Just a Hook(线段树,成段更新,懒惰标记)

Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 18384    Accepted Submission(s): 9217 Problem Description In the game of DotA, Pudge's meat hook is actually the most horrible thing