zoukankan      html  css  js  c++  java
  • 线段树和树状数组

    线段树

    引入1:有n个数(n<=50000)个数,m(m<=50000)次询问。每次询问区间L到R的数的和。要求输出每一次询问的结果......
    分析:
    1.用前缀和问题进行求解:再开一个数组(暂且记为b[n],设n个数所组成的数组为a[n]),b[i]用来记录从a[1]到a[i]的所有数字的和(即 b[1]=a[1],b[2]=b[1]+a[2],...,b[i]=b[i-1]+a[i])。这样对于m次询问,只需要对数组b进行相应的操作就可以了。
    eg:求数组a中区间[L,R]的所有数字的和sum,即sum=a[L]+a[L+1]+...+a[R-1]+a[R]。由于我们之前已经对数组a求了前缀和,并得到前缀和数组b[n]。因此对b[n]进行操作就可以了,即sum=b[R]-b[L-1]。
    2.用线段树进行求解:这里先告诉大家可以用线段树来进行求解,具体如何求解我们可以等对线段树进行一定的讲解后再来进行求解。
    引入2:RMQ(Range Minimum/Maximum Query)问题
    有n(n<=50000)个数,m(m<=50000)次询问,要求每次输出区间[L,R]的数的最大值。
    分析:
    1.用ST表进行求解:一种利用dp思想求解区间最值的倍增算法。
    定义:f(i,j)表示[i,i+\(2^j\)−1]这段长度为2^j的区间中的最大值。
    预处理:f(i,0)=a[i]。即[i,i]区间的最大值就是a[i]。
    状态转移:将[i,j]平均分成两段,一段为[i,i+2(j−1)−1],另一段为[i+2(j−1),i+2j−1]。两段的长度均为2(j−1)。f(i,j)的最大值即这两段的最大值中的最大值。得到f(i,j)=max{f(i,j−1),f(i+2^(j−1),j−1)}。
    查询:若需要查询的区间为[i,j],则需要找到两个覆盖这个闭区间的最小幂区间。
    这两个区间可以重复,因为两个区间是否相交对区间最值没有影响。(如下图)

    当然求区间和就肯定不行了。这也就是RMQ的限制性。
    因为区间的长度为j−i+1,所以可以取k=\(\log_2 (j-i+1)\)
    则所求即为max{f(i,k),f(j−2k+1,k)}。
    参考代码如下:

    #include<iostream>//这段代码以询问最大值为例
    #include<algorithm>
    #include<cmath>
    using namespace std;
    int a[50005];
    int f[50005][16];
    int main()
    {
        ios::sync_with_stdio(false);
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        cin>>a[i];
        for(int i=1;i<=n;i++)
        f[i][0]=a[i];//可以参照上文的预处理部分
        /*
        注意外部循环从j开始,
        因为初始状态为f[i][0],
        以i为外层会有一些状态遍历不到。
        */ 
        for(int j=1;j<=16;j++)//2^16>50000足够了
           for(int i=1;i<=n;i++)//可以参照上文的状态转移部分
              if(i+(1<<j)-1<=n)//判断区间最右端的值是否超过n
              {
                  f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
              }
        int l,r;
        while(m--)//处理m次询问
        {
            cin>>l>>r;
            int k=(int)(log((double)(r-l+1))/(log(2.0)));
            cout<<max(f[l][k],f[r-(1<<k)+1][k])<<endl;
        }
        return 0;
    }
    

    参考例题:
    【模板】ST表
    质量检测
    2.用线段树进行求解:这里先告诉大家可以用线段树来进行求解,具体如何求解我们可以等对线段树进行一定的讲解后再来进行求解。
    概念:
    线段树是用一种树状结构来存储一个连续区间的信息的数据结构。它主要用于处理一段连续区间的插入,查找,统计,查询等操作。
    复杂度:设区间长度是n,所有操作的复杂度是logn级别。

    • 线段树也是一种树形结构,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶节点。线段树是建立在线段的基础上,每个节点都代表了一条线段[a,b],长度为 1 的线段称为元线段。非元线段都有两个子节点,左节点代表的线段为[a,(a+b)/2],右节点代表的线段为[((a+b)/2)+1,b]。
    • 线段[1,7]的线段树和[2,5]的分解

      线段树的几点性质:
    • 线段树是平衡的二叉树,最大深度logn(n为线段树所表示区间的长度)
    • 树中的每一个节点代表对应一个区间(叶子节点是一个点……)
    • 每个节点(所代表的区间)完全包含它的所有孙节点
    • 对于任意两个节点(所代表的区间):要么完全包含,要么互不相交
    • 在进行区间操作和统计时把区间等价转化成若干个子区间(logn个)的相同操作
    • 任意的线段[a,b]在线段树的查询或查找过程中把这个线段最多分成log(b-a)份(显然每一层最多两个区间)
    • So,线段树除建树外的操作都是log(n)级别的复杂度。
      使用线段树可以快速查找某个节点在若干条区间中出现的次数、某个区间的最大或最小值、某个区间的和等问题。这时就需要了解线段树的 3 个基本操作:建树、更新和查询。

    例1:
    给你一个长度为n的序列,有如下操作,将第i个数加或减x,求区间Li到Ri的和。
    解析:
    1:建树
    -建树时,可以使用结构体的方法,将节点的左右端点、记录的信息(最大值、最小值、区间和等)存储进去;也可使用一个数组保存,使用数组时,省略了左右端点,因为在递归过程中,左右端点可以通过值传递的方式得到。建树就是一个遍历二叉树的过程,从根节点出发,依次遍历左子树,到元线段返回,再遍历右子树,直到遍历结束。
    建树过程代码如下:

    void buildTree(int p, int l,int r)//p:数组下标;l:区间左端点;r:区间右端点
    {
        if(l==r)  
        {
            tree[p]=arr[l];
            return;
        }
        int mid=(l+r)/2;
        buildTree(p*2,l,mid);//建立左子树
        buildTree(p*2+1,mid+1,r);//建立右子树
        tree[p]=tree[p*2]+tree[p*2+1];
    }
    

    -更新操作和查询操作类似,时间复杂度都为O(logn)。当执行更新操作时,要遍历到叶子节点,再层层递归更新,而查询操作只需要查询到包括的区间为止。
    更新操作如下:

    void change(int p,int l, int r,int x, int num)//给下标为x的位置加上num
    {
        if(l==r)
        {
            tree[p]+=num;
            return;
        }
        int mid=(l+r)/2;
        if(x<=mid)
        {
            change(p*2,l,mid,x,num);
        }
        else
        {
            change(p*2+1,mid+1,r,x,num);
        }
        tree[p]=tree[p*2]+tree[p*2+1];
    }
    

    查询操作如下:

    int query(int p,int l,int r, int x, int y)
    {
        if(x<=l&&r<=y)
        return tree[p];
        int mid=(l+r)/2;
        if(y<=mid)
        return query(p*2,l,mid,x,y);
        if(x>mid)
        return query(p*2+1,mid+1,r,x,y);
        return (query(p*2,l,mid,x,y)+query(p*2+1,mid+1,r,x,y));
    }
    

    完整代码如下:

    #include<iostream>
    using namespace std;
    int arr[10005],tree[10005];
    void buildTree(int p, int l,int r)//p:数组下标;l:区间左端点;r:区间右端点
    {
        if(l==r)  
        {
            tree[p]=arr[l];
            return;
        }
        int mid=(l+r)/2;
        buildTree(p*2,l,mid);//建立左子树
        buildTree(p*2+1,mid+1,r);//建立右子树
        tree[p]=tree[p*2]+tree[p*2+1];
    }
    void change(int p,int l, int r,int x, int num)//给下标为x的位置加上num
    {
        if(l==r)
        {
            tree[p]+=num;
            return;
        }
        int mid=(l+r)/2;
        if(x<=mid)
        {
            change(p*2,l,mid,x,num);
        }
        else
        {
            change(p*2+1,mid+1,r,x,num);
        }
        tree[p]=tree[p*2]+tree[p*2+1];
    }
    int query(int p,int l,int r, int x, int y)
    {
        if(x<=l&&r<=y)
        return tree[p];
        int mid=(l+r)/2;
        if(y<=mid)
        return query(p*2,l,mid,x,y);
        if(x>mid)
        return query(p*2+1,mid+1,r,x,y);
        return (query(p*2,l,mid,x,y)+query(p*2+1,mid+1,r,x,y));
    }
    int main()
    {
        ios::sync_with_stdio(false);
        int n,m,k;//n个元素,m次修改,k次询问
        cin>>n>>m>>k;
        for(int i=1;i<=n;i++)
        cin>>arr[i];
        buildTree(1,1,n);
       while(m--)
        {
            int x,num;
            cin>>x>>num;
            change(1,1,n,x,num);
        }
        while(k--)
        {
            int x,y;
            cin>>x>>y;
            cout<<query(1,1,n,x,y)<<endl;
        }
        return 0;
    }
    
    

    线段树和树状数组的题单:https://ac.nowcoder.com/acm/problem/collection/621
    参考资料:
    1.https://www.cnblogs.com/YSFAC/p/7189571.html
    2.https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html

  • 相关阅读:
    怎么自定义修改CnBlogs博客园主题模板css样式
    前端怎么避免无效的请求,减轻服务器负载
    360浏览器奇葩问题:非得打开一次控制台才能登录(try catch finally 用法)
    react中<br/>不换行、多个&nbsp;只显示一个空格的问题 dangerouslySetInnerHTML
    webpack压缩文件错误:ERROR in bundle.js from UglifyJs
    webpack css单独打包 及extract-text-webpack-plugin插件
    css如何引入第三方字体
    计数排序
    接口
    抽象方法和抽象类
  • 原文地址:https://www.cnblogs.com/Acapplella/p/13303982.html
Copyright © 2011-2022 走看看