编程语言
首页 > 编程语言> > 算法提高课 第四章 数据结构之树状数组

算法提高课 第四章 数据结构之树状数组

作者:互联网

一、介绍

功能

原理

image
c[x]:以x结尾的长度lowbit(x)的所有数的和
image
父节点找所有子节点(求和操作):c[x] = a[x] + c[x-1] + ... + c[lowbit(x-1)],x为偶数时,每一次去掉最后一个1;x为奇数时,没有子节点
子节点找父节点(修改操作):p = x + lowbit(x),修改当前结点时,一连串的与该结点有关的父节点都要被修改

建立

题目

241. 楼兰图腾

思路:个数统计+树状数组(O(nlogn))

题解

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N = 2e5 + 20;
int n;
int a[N];
int tr[N];
int lower[N],upper[N];

inline int lowbit(int x)
{
    return x & -x;
}
void add(int x,int c)
{
    for(int i = x;i<=n;i+=lowbit(i))
    {
        tr[i] += c;
    }
}
LL sum(int x)
{
    LL res = 0;
    for(int i = x;i;i-=lowbit(i))
    {
        res += (LL)tr[i];
    }
    return res;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1;i<=n;i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ )
    {
        int x = a[i];
        upper[i] = sum(n) - sum(x-1);//查询左边比a[i]大的数的个数
        lower[i] = sum(x-1);//查询左边比a[i]小的数的个数
        add(x,1);//添加a[i]
    }
    
    LL ans1 = 0,ans2 = 0;
    memset(tr,0,sizeof tr); //清空线段树
    for(int i = n;i>=1;i--)//从右往左求右边比a[i]大 or 小的数
    {
        int x = a[i];
        ans1 += (LL)upper[i]*(sum(n) - sum(x));//左边大的乘以右边大的就是构成V的个数
        ans2 += (LL)lower[i]*sum(x-1);//左边小的乘以右边小的就是构成^的个数
        add(x,1);//添加a[i]
    }
    printf("%lld %lld\n",ans1,ans2);
    return 0;
}

242. 一个简单的整数问题

思路:树状差分数组 O(nlogn)

题解

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int tr[N];//差分树状数组:支持区间/单点查询,区间/单点修改
int n,m;
int lowbit(int x)
{
    return x & -x;
}
void add(int x,int c)
{
    for(int i = x;i<=n;i+=lowbit(i))
    {
        tr[i] += c;
    }
}
int sum(int x)
{
    int res = 0;
    for(int i = x;i;i-=lowbit(i))
    {
        res += tr[i];
    }
    return res;
}
void insert(int l,int r,int c) //差分的插入操作均为单点修改,符合树状数组的性质
{
    add(l,c);// tr[l] += c;
    add(r+1,-c); //tr[r+1] -= c;
    return;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        insert(i,i,x);
    }
    while (m -- )
    {
        char op[2];
        int l,r,d,x;
        scanf("%s", &op);
        if(op[0] == 'C')
        {
            scanf("%d%d%d", &l, &r,&d);
            insert(l,r,d);
        }
        else
        {
            scanf("%d", &x);
            cout<<sum(x)<<endl;//通过差分还原某个数的操作是区间求和,符合树状数组性质
        }
    }
    return 0;
}

243. 一个简单的整数问题2

思路

image

题解

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N = 1e5 + 10;

int n,m;
LL tr1[N],tr2[N];

int lowbit(int x)
{
    return x & -x;
}
void add(LL tr[],int x,LL c) //树状数组单点修改
{
    for(int i = x;i<=n;i+=lowbit(i))
    {
        tr[i] += c;
    }
}
LL sum(LL tr[],int x) //树状数组区间查询
{
    LL res = 0;
    for(int i = x;i;i-=lowbit(i))
    {
        res += (LL)tr[i];
    }
    return res;
}
void insert(int l,int r,LL c) //差分数组区间修改
{
    add(tr1,l,c);
    add(tr1,r+1,-c);
    add(tr2,l,l*c);
    add(tr2,r+1,-(r+1)*c);
}
LL get(int x) //求和公式
{
    LL res = (LL)(x+1)*sum(tr1,x)-sum(tr2,x);
    return res;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1;i<=n;i++)
    {
        int x;
        scanf("%d", &x);
        insert(i,i,x);
    }
    while (m -- )
    {
        char op[2];
        int l,r,d;
        scanf("%s", op);
        if(op[0] == 'C')
        {
            scanf("%d%d%d", &l,&r,&d);
            insert(l,r,d);
        }
        else
        {
            scanf("%d%d", &l,&r);
            cout<< get(r) - get(l-1) <<endl;
        }
    }
    return 0;
}

244. 谜一样的牛

思路:树状数组+二分 O(n(logn)^2)

先思考常规做法:数组A[i]表示前面有A[i]个数小于第i个位置的数,而A[i]是1-n的全排列,故我们可以A[i]从右往左遍历,对于第i个数而言,前i-1个数中有a[i]个比它小,即在这剩余的数中它是第a[i]+1小的,排序选出第a[i]+1个小的数,并从剩余的数中删除。时间复杂度为O(n^2logn),显然不符合要求。
树状数组优化:tr[i]表示当前i的个数(0 or 1),从右向左,二分查找出最小的x,使得sum[x]=a[i]+1,sum[x]表示1-x中有多少个剩余的数可用,因为是最小的x,所以tr[x]一定是1,且x一定是1-x中第a[i]+1小的数。x即为所求。最后一定要从tr中删除x

题解

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int a[N],n;
int tr[N];//tr[x]:表示当前x的可用的个数
int ans[N];
int lowbit(int x)
{
    return -x & x;
}
void add(int x,int c)
{
    for(int i = x;i<=n;i+=lowbit(i))
    {
        tr[i] += c;
    }
    return;
}
int sum(int x)
{
    int res = 0;
    for(int i = x;i;i-=lowbit(i))
    {
        res += tr[i];
    }
    return res;
}

int main()
{
    cin>>n;
    for(int i = 2;i<=n;i++) //注意从2开始循环
    {
        cin>>a[i];
    }
    for(int i = 1;i<=n;i++) add(i,1); //初始化为1,可用
    for(int i = n;i;i--)
    {
        int l = 1,r = n,x = a[i] + 1;
        while(l<r) //二分找出最小的x,使得sum(x) = a[i] + 1
        {
            int mid = l+r>>1;
            if(sum(mid) >= x) r = mid;
            else l = mid + 1;
        }
        ans[i] = r;
        add(r,-1); //删除x
    }
    for (int i = 1; i <= n; i ++ ) cout<<ans[i]<<endl;
    return 0;
}

标签:include,树状,int,sum,个数,数组,数据结构,第四章
来源: https://www.cnblogs.com/zjq182/p/16637864.html