• 树状数组


    定义

         树状数组或者二叉索引树也称作Binary Indexed Tree,又叫做Fenwick树。 它的查询和修改的时间复杂度都是log(n),空间复杂度则为O(n).

         树状数组可以将线性结构转化成树状结构,从而进行跳跃式扫描,通常使用在高效的计算数列的前缀和,区间和。

    理解

         在树状数组之前如果求和的话,一般采用循环遍历的方式进行累加计算,跳跃间隔为 1。

         如果利用树状数组求和的话,跳跃间隔为 x&(-x),这样不仅可以利用二进制去加快计算,也能够减少循环次数从而达到减少程序运行的时间。

    实现

    通过图片可以直接得到:

    sum[1] = A[1]

    sum[2] = A[1] + A[2]

    sum[3] = A[3]

    sum[4] = A[1] + A[2] + A[3] + A[4]

    sum[5] = A[5]

    sum[6] = A[5] + A[6]

    sum[7] = A[7]

    sum[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]

    sum[9] = A[9]

    得到  //k为i的二进制中从最低位到高位连续零的长度

    那么如何求2^k (k为i的二进制中从最低位到高位连续零的长度)

    K=i&(-i)

    Tip:负数的二进制求法

    ①求负数的绝对值的二进制

    ②反码(0->1,1->0)

    ③补码(最后一位加1)

    例:,所以-5的二进制为1011。

    一维线段树

    单点更新 + 区间查询

    单点更新

    更新A[i]时,涉及到的有

    例:更新A[5]时,涉及到的有sum[5],sum[6],sum[8]

     

    区间查询

    令 SUMi 为前i项和,则

    例:前七项和为

    所以区间[L,R]的和为 

    代码

    void add(int p, int x)//给位置p增加x
    { 
      while(p <= n) sum[p] += x, p += p & -p;
    }
    int ask(int p)//求位置p的前缀和
    { 
      int res = 0;
      while(p) res += sum[p], p -= p & -p;
      return res;
    }
    int range_ask(int l, int r)//区间求和
    { 
      return ask(r) - ask(l - 1);
    }

    单点查询 + 区间修改 

    通过差分实现,原数组为A[i],令d[i]=A[i]-A[i-1](A[0]=0)

     

    单点查询 

    ,通过求d[i]的前缀和查询。

     

    区间修改 

    当给区间[l,r]加上x的时候,a[l]与前一个元素a[l−1]的差增加了x,a[r+1]与a[r]的差减少了x。

    根据d[i]数组的定义,只需给d[l]加上x,给d[r+1]减去x即可。

    代码

    void add(int p, int x) //这个函数用来在树状数组中直接修改
    {
      while(p <= n) sum[p] += x, p += p & -p;
    }
    void range_add(int l, int r, int x) //给区间[l, r]加上x
    { 
      add(l, x);
      add(r + 1, -x);
    }
    int ask(int p) //单点查询
    { 
      int res = 0;
      while(p) res += sum[p], p -= p & -p;
      return res;
    }

    区间修改 + 区间查询

    通过差分实现,原数组为A[i],令d[i]=A[i]-A[i-1](A[0]=0)

    维护两个数组sum1[i]=d[i],sum2[i]=i*d[i]

     

    区间查询

    区间[L,R]的和为={(R+1)*sum1[R]前缀和-sum2[R]前缀和} - {L*sum1[L-1]前缀和-sum2[L-1]前缀和}。

     

    区间修改

    sum1[L]+=x,sum1[R+1]-=x

    sum2[L]+=x*L,sum2[R+1]-=x*(R+1)

    代码

    void add(ll p, ll x)
    {
      for(int i = p; i <= n; i += i & -i)
        sum1[i] += x, sum2[i] += x * p;
    }
    void range_add(ll l, ll r, ll x)
    {
      add(l, x);
      add(r + 1, -x);
    }
    ll ask(ll p)
    {
      ll res = 0;
      for(int i = p; i; i -= i & -i)
        res += (p + 1) * sum1[i] - sum2[i];
      return res;
    }
    ll range_ask(ll l, ll r)
    {
      return ask(r) - ask(l - 1);
    }

    二维线段树

    单点修改 + 区间查询

    一维很容易扩展(魔改)到二维

    代码

    int tree[100][100],n;
    void add(int x, int y, int z)  //将点(x, y)加上z
    { 
        int memo_y = y;
        while(x <= n)
        {
            y = memo_y;
            while(y <= n)
                tree[x][y] += z, y += y & -y;
            x += x & -x;
        }
    }
    int ask(int x, int y)  //求左上角为(1,1)右下角为(x,y) 的矩阵和
    {
        int res = 0, memo_y = y;
        while(x)
        {
            y = memo_y;
            while(y)
                res += tree[x][y], y -= y & -y;
            x -= x & -x;
        }
        return res;
    }
    int main()
    {
      int i,j,w,v,x,y;
      scanf("%d",&n);
      for(i=1;i<=n;i++)
       for(j=1;j<=n;j++)
       {
          scanf("%d",&v);
          add(i,j,v);
       }
      while(true)
      {
          scanf("%d%d",&x,&y);
          printf("(%d,%d) == %d
    ",x,y,ask(x,y));
      }
      system("pause");
      return 0;
    }

    区间修改 + 单点查询

    通过学习二维差分我们可以知道

    令  。

    区间修改

    当修改区间 [ 左上角 -(xa , ya), 右下角 - (xb , yb) ] 时,改变量为 c ,则d[xa][ya] += c , d[xa][yb+1] -= c , d[xb+1][ya] -= c , d[xb+1][yb+1] += c

    单点查询

    由d[i][j]的推导式可得 

    用树状数组维护d[i][j]即可。 

    代码

    void add(int x, int y, int z)
    { 
        int memo_y = y;
        while(x <= n)
        {
            y = memo_y;
            while(y <= n)
                tree[x][y] += z, y += y & -y;
            x += x & -x;
        }
    }
    void range_add(int xa, int ya, int xb, int yb, int z)  //区间修改
    {
        add(xa, ya, z);
        add(xa, yb + 1, -z);
        add(xb + 1, ya, -z);
        add(xb + 1, yb + 1, z);
    }
    int ask(int x, int y)  //求(x,y)的值
    {
        int res = 0, memo_y = y;
        while(x)
        {
            y = memo_y;
            while(y)
                res += tree[x][y], y -= y & -y;
            x -= x & -x;
        }
        return res;
    }

    区间修改 + 区间查询

    令  ,那么关于点(x , y)的前缀和为

    由于x,y已知,我们可以知道每个 d[h][k] 出现过多少次。d[1][1] 出现了x * y次,d[1][2] 出现了x * (y - 1)次,所以 d[h][k] 出现了(x - h + 1) * (y - k + 1)次。

    所以关于点(x , y)的前缀和可以转换为

    将其展开

    得到四个子项的和。

    所以利用树状数组维护  即可。

     代码

    void add(ll x, ll y, ll z)
    {
        for(int X = x; X <= n; X += X & -X)
            for(int Y = y; Y <= m; Y += Y & -Y)
            {
                t1[X][Y] += z;
                t2[X][Y] += z * x;
                t3[X][Y] += z * y;
                t4[X][Y] += z * x * y;
            }
    }
    void range_add(ll xa, ll ya, ll xb, ll yb, ll z)  //(xa, ya) 到 (xb, yb) 的矩形
    {
        add(xa, ya, z);
        add(xa, yb + 1, -z);
        add(xb + 1, ya, -z);
        add(xb + 1, yb + 1, z);
    }
    ll ask(ll x, ll y)
    {
        ll res = 0;
        for(int i = x; i; i -= i & -i)
            for(int j = y; j; j -= j & -j)
                res += (x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x + 1)*t3[i][j]+t4[i][j];
        return res;
    }

    此文章参考胡小兔

    本博客仅为本人学习,总结,归纳,交流所用,若文章中存在错误或有不当之处,十分抱歉,劳烦指出,不胜感激!!!
  • 相关阅读:
    列表常用方法及演示
    print >> 重定向
    简单字符串过滤练习
    None,Python 的 Null 对象,布尔值
    Time模块简单学习
    str()与repr(),input()与raw_input()
    布尔数,__nonzero__()
    字符串内建函数
    标准类型内建函数
    客户端precommit hook参数
  • 原文地址:https://www.cnblogs.com/VividBinGo/p/11423498.html
Copyright © 2020-2023  润新知