• 树状数组


    树状数组

    对于区间之间的增删查改,如果单纯按照之前的想法就是O(1)查询,然后O(n)的时间复杂度去进行修改。

    而树状数组查询和修改都是O(logn)的复杂度

    接下来详细讲一下树状数组的基本操作

    数组A(原数组) /// 数组C(树状数组)

    梳理图

    原理: 找出每个数的二进制最低位的1,然后其他1归零,剩下的这个二进制数就是C数组元素的个数(也就是求lowbit)可以直接这么求(证明暂省)



    luoguP3374

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=5e5+7;
    int a[maxn];
    int c[maxn]={0};
    inline int lowbit(int x)
    {
        return x&(-x);
    }
    int query(int x)///区间求和
    {
        int sum=0;
        while (x>0)
        {
            sum+=c[x];
            x=x-lowbit(x);
        }
        return sum;
    }
    void modify(int idx,int math,int k)///修改 k是边界 idx修改地址 math修改值,修改一个节点
    {
        while (idx<=k)
        {
            c[idx]+=math;
            idx+=lowbit(idx);
        }
    
    }
    int all(int a,int b)///区间查询
    {
        return query(b)-query(a-1);
    }
    int main()
    {  
       int n,m,x;
       scanf("%d%d",&n,&m);
       int a,b,c;
       for (int i=1;i<=n;++i)
       {
           scanf("%d",&x);
           modify(i,x,n);
       }
       while (m--)
       {
          scanf("%d%d%d",&a,&b,&c);
          if (a==1)
          {
             modify(b,c,n);
          }
          else if (a==2)
          {
            cout<<all(b,c)<<endl;
          }
       }
      return 0;
    }
    

    当然树状数组也支持区间修改和单点查询(一般采用差分数组和前缀和)

    此时的C数组就是差分数组////B数组就是树状数组////A数组就是储存数组

    luoguP3368

    思维转化,区间修改的话不妨弄一个差分数组C,然后C[i]=A[i]-A[i-1]对于修改之后的值在区间[a,b]上增加一个d

    只需要对于C[a]+d ///C[b+1]减去一个d即可实现区间修改操作

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=5e5+7;
    int A[maxn]={0};///储存数组
    int B[maxn]={0};///树状数组
    int C[maxn]={0};///差分数组
    inline int lowbit(int x)
    {
        return x&(-x);
    }
    int query(int x)///和上面的操作一样
    {
        int sum=0;
        while (x>0)
        {
            sum+=B[x];
            x=x-lowbit(x);
        }
        return sum;
    }
    void modify(int idx,int math,int k)
    {
        while (idx<=k)
        {
            B[idx]+=math;
            idx+=lowbit(idx);
        }
    
    }
    
    int main()
    {  
       int n,m;
       cin>>n>>m;
       int a,b,c,d;
       for (int i=1;i<=n;++i)
       {
           cin>>A[i];
           C[i]=A[i]-A[i-1];
           modify(i,C[i],n);///改动的地方只是把差分数组当作原来的原数组
       }
       while (m--)
       {
          cin>>a;
          if (a==1)
          {  
             cin>>b>>c>>d;
             modify(b,d,n);///转化一下就是两点之间的修改操作
             modify(c+1,-d,n);
          }
          else if (a==2)
          { 
            cin>>b;
            cout<<query(b)<<endl;///从1到b的区间和
          }
       }
      return 0;
    }
    

    一般讲来树状数组支持的就是单点查询、区间修改和区间求和、单点查改

    其实严格意义上还可以进行树状数组维护区间最值(当然树套树我是不会的啦)//doge

    如果每一次都直接遍历一遍max就过于慢速

    在原来区间和的基础之上C[i]储存的是A[i-lowbit(i)+1]一直到A[i]之间的数值,区间最值问题当然不能简单的这样区域合并

    C[i]此时应该储存的是C[i-lowbit+1]到C[i]之间的最大值

    单纯直接按照树状数组的单点修改操作是可行的,但对于区间修改复杂度就是O(n*logn)

    void modify(int idx,int math,int k)///修改 k是边界 idx修改地址 math修改值
    {
          while (idx<=k)
        {
            c[idx]=max(c[idx],math);
            idx+=lowbit(idx);
        }
    }
    

    所以为了节省时间一般采用下面那种

    观察下之前的区间加法图,譬如我现在选择C[4]接下来我就直接将A[4]的值赋值给C[4],然后再直接与C[3],C[2]进行比较

    不难发现,C[i]只与C[i-2^x]存在关系

    void modify(int idx,int math,int k)///修改 k是边界 idx修改地址 math修改值
    { 
        c[idx]=math;///该区域肯定要包括自己,所以首先把自己加入进去
        while (idx<=k)
        {
            for (int i=1;i<lowbit(idx);i<<=1)///直接枚举
                c[idx]=max(c[idx],c[idx-i]);
            idx+=lowbit(idx);
        }
    }
    

    对于区间查询就只需要找出区间覆盖子段

    1.第一种情况就是区间[x,y]包含[y-lowbit(y),y],即

    y-lowbit(y)>x,此种状态下就直接枚举区间比较区间最大值

    2.不包括的情况,只能将y-1,

    用原来的a[y]去进行比较

    int query(int x,int y)///求区间最大值
    {
        if(x==y)return a[x];///最后区间只有一个数那就是最大值
        if(y-lowbit(y)>x)return max(c[y],query(x,y-lowbit(y)));
        else return max(a[y],query(x,y-1));
    }
    

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=4e5+7;
    #define  ll long long
    int a[maxn];
    int c[maxn]={0};
    const int INF=0x3f3f3f3f;
    inline int lowbit(int x)
    {
        return x&(-x);
    }
    int query(int x,int y)///求区间最大值
    {
        if(x==y)return a[x];///最后区间只有一个数那就是最大值
        if(y-lowbit(y)>x)return max(c[y],query(x,y-lowbit(y)));
        else return max(a[y],query(x,y-1));
    }
    void modify(int idx,int math,int k)///修改 k是边界 idx修改地址 math修改值
    {
        c[idx]=math;///该区域肯定要包括自己,所以首先把自己加入进去
        while (idx<=k)
        {
            for (int i=1;i<lowbit(idx);i<<=1)///直接枚举
            {
                    c[idx]=max(c[idx],c[idx-i]);
            }
            idx+=lowbit(idx);
        }
    }
    
    int main()
    {
        memset(a,0,sizeof(a));
        memset(c,0,sizeof(c));
        int n;
        scanf ("%d",&n);
        for (int i=1;i<=n;++i)
        {
            scanf ("%d",a+i);
            modify(i,a[i],n);
        }
        int t;
        scanf("%d",&t);
        while (t--)
        {
          int x,y;
          scanf("%d%d",&x,&y);
          printf("%d
    ",query(x,y));
        }
        return 0;
    }
    
    

    之前参考的时候我觉得 c[idx]=math;///该区域肯定要包括自己,所以首先把自己加入进去这一点应该是在while外面的,因为我之前举例子的时候C4不包括A4所以只能手动加入,那样的话就只要加入一次,你放在while里面,idx在变化所以就不太对,当然你如果直接c[idx]=a[idx]也就避免了这种问题,还会快一点

    个人理解,有误之处请指正

    参考博客1
    参考博客2

    齐芒行,川锋明!
  • 相关阅读:
    12.20个人任务总结
    典型用户和用户场景描述
    12.19冲刺总结
    12月18日任务总结
    构建之法阅读笔记02
    构建之法阅读笔记03
    构建之法阅读笔记01
    软件工程个人作业01
    内容提供者
    Pull解析Xml
  • 原文地址:https://www.cnblogs.com/qimang-311/p/13380293.html
Copyright © 2020-2023  润新知