zoukankan      html  css  js  c++  java
  • 树状数组的基操

    前沿:数据结构

    任何一个数据结构都逃不开这么几个东西:增,删,改,查

    -------------------------------------------------------------------

    树状数组,也叫做二叉索引树(BIT)。

    是一个用来进行区间运算的数据结构。

    在一定程度上可以代替线段树。

    树状数组有以下几个特征:
    1.巧妙地利用了位运算

    2.巧妙地结合了树的数据结构的思想来处理区间问题

    3.树状数组的本质还是用来维护序列的前缀

    --------------------------------------------------------------------

    树状数组有以下几个基本操作:

    1.树的节点转移(位运算)//是2,3,4,的根基

    2.单点修改

    3.区间修改

    4.区间查询

    --------------------------------------------------------------------

    首先树状数组的大致情况,我给出了一下资料:

    算法竞赛入门经典-训练指南-第194-197页 

    --------------------------------------------------------------------

    树状数组的时间复杂度和空间复杂度:

    1、时间复杂度:

    O(logn),这是按照一次更改或者查询来讲的(局部)

    O(nlogn),这是按照更新所有n个值来讲的(整体)

    2、空间复杂度:

    O(n)(一维)

    O(n*m)(二维)

    --------------------------------------------------------------------

    接下来就是以上操作的具体实现的代码:

    等等,先给出一些数组

    c[]表示树状数组,a[]表示原序列

    好了,let‘s go

    1.树的节点转移(位运算)//关于这个函数的作用和这样做的原因,在上面提到的书中有讲到

    int lowbit(int x)
    {
        return x&(-x);
    }

    2,单点修改

    与线段树相同的是,树状数组如果修改了某一个点的话,势必会影响到上层区间的维护的值,那么单点修改的关键就是怎么维护上层区间的维护的值

    下面我给出一个函数:

    void add(int d,int x)
    {
        while(x<=n)
        {
            c[x]+=d;
            x+=lowbit(x);
        }
    }

    3.区间修改

    这个涉及到了差分数组的运用

    有关差分数组和这一部分的数学证明,我在一下链接种给出:
    https://www.cnblogs.com/lcf-2000/p/5866170.html

    void qujian_update(int* arr,int x,int d)
    {
        while(x<=n)
        {
            arr[x]+=d;
            x+=lowbit(x);
        }
    }

    4.区间查询

    int query(int* arr,int x)
    {
        int ret=0;
        while(x>0)
        {
            ret+=arr[x];
            x-=lowbit(x);
        }
        return ret;
    }

    --------------------------------------------------------------------

    现在,我给出一份完整的函数代码:
    第一份:没有区间修改的树状数组

    #include<bits/stdc++.h>
    using namespace std;
    int c[20];
    int lowbit(int x)//作用:返回查找结点的下标
    {
        return x&-x;
    }
    int sum(int x)//返回1~x的数组的前缀和
    {
        int ret=0;
        while(x>0)
        {
            ret+=c[x];
            x-=lowbit(x);
        }
        return ret;
    }
    void add(int x,int d)//即能够起到修改区间的作用,又能够起到构建树状数组(二叉索引树)的作用
    {
        while(x<=n)
        {
            c[x]+=d;
            x+=lowbit(x);
        }
    }
    int query(int l,int r)//作用:查询l~r的区间和,注意:sum[l,r]=c[r]-c[l-1];
    {
        int ret=0;
        ret=sum(r)-sum(l-1);
        return ret;
    }
    int main()
    {
        int n,a[10];
        cin>>n;
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;++i)
        {
            cin>>a[i];//输入的数组
            add(i,a[i]);//不能从0开始,因为0会让lowbit进入死循环
        }
        string s;
        while(cin>>s)
        {
            if(s=="query")//查询的命令
            {
                int l,r;
                cin>>l>>r;
                cout<<query(l,r)<<endl;
            }
            else if(s=="add")//修改的命令,在a[x]上加上d => a[x]=a[x]+d
            {
                int d,x;
                cin>>d>>x;
                add(x,d);
            }
            else if(s=="change")//单点更新=>a[x]=d
            {
                int d,x;
                cin>>d>>x;
                add(x,d-a[x]);
            }
        }
        return 0;
    }

    第二份:带区间修改的树状数组

    #include<bits/stdc++.h>
    #define mem(a,b) memset(a,b,sizeof(a))
    #define maxn 1200000
    #define mod 123456789
    using namespace std;
    typedef long long ll;
    int c1[maxn],n,c2[maxn],a[maxn];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int d,int x)
    {
        while(x<=n)
        {
            c[x]+=d;
            x+=lowbit(x);
        }
    }
    void qujian_update(int* arr,int x,int d)
    {
        while(x<=n)
        {
            arr[x]+=d;
            x+=lowbit(x);
        }
    }
    int query(int* arr,int x)
    {
        int ret=0;
        while(x>0)
        {
            ret+=arr[x];
            x-=lowbit(x);
        }
        return ret;
    }
    int main()
    {
        cin>>n;
        mem(c1,0);
        mem(c2,0);
        mem(a,0);
        for(int i=1;i<=n;++i)
        {
            cin>>a[i];
            qujian_update(c1,i,a[i]-a[i-1]);
            qujian_update(c2,i,(i-1)*(a[i]-a[i-1]));
        }
        string s;
        while(cin>>s)
        {
            if(s=="change")
            {
                int l,r,d;
                cin>>l>>r>>d;
                qujian_update(c1,l,d);
                qujian_update(c1,r+1,-d);
                qujian_update(c2,l,d*(l-1));
                qujian_update(c2,r+1,-d*r);
            }
            else
            {
                int l,r;
                cin>>l>>r;
                int tmp1=(l-1)*query(c1,l-1)-query(c2,l-1);
                int tmp2=r*query(c1,r)-query(c2,r);
                int ans=tmp2-tmp1;
                cout<<ans<<endl;
            }
        }
        return 0;
    } 

     -----------------------------------------

    BIT还可以用来维护区间,还可以统计逆序数(维护比当前数字小的个数和总和)

  • 相关阅读:
    浏览器组成
    Go!!!
    产假计算器地址
    flex 纵向布局,垂直换行,没有撑开父盒子宽度,求解??
    毕业档案
    进程与线程
    事件循环
    回调地狱
    错误优先回调
    组件 v-if 小心哦
  • 原文地址:https://www.cnblogs.com/JJsnow/p/9969784.html
Copyright © 2011-2022 走看看