ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-09-04 19:33:27  阅读:184  来源: 互联网

标签:include 树状 int sum 个数 数组 数据结构 第四章


一、介绍

功能

  • 快速求前缀和 O(logn)
  • 修改某一个数 O(logn)

原理

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))

  • y1~yn是1到n的一个全排列,对于某个位置的数而言,左边比它大的数的个数与右边比它大的数的个数的乘积就是组成'V'型的个数。
  • 同理,左边比它小的数的个数与右边比它小的数的个数的乘积就是组成'^'型的个数。
  • 因此,我们可以维护一个lower和upper数组,lower[i]表示比a[i]小的个数,upper[i]表示比a[i]大的个数。从左到右,边统计边查询,这样lower和upper就表示某个数左边比它大或小的数的个数。使用树状数组可以快速求出某个区间的和与修改某个数,c[x]来表示x出现的次数,sum(n)-sum(x)表示比x大的数的个数,sum(x-1)表示小于x的数的个数。
  • 同理,从右往左边统计边查询,可以处理处某个数右边比它大或小的数的个数。

题解

#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)

  • 题目中有区间修改,单点查询,符合差分数组的性质,而差分单点查询本质操作是求前缀和(O(n)),一定超时,因为差分的区间修改本质是单点修改,我们可以把差分数组构造成树状数组,使得所有操作为O(logn)的

题解

#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

  • 题目中有区间修改,必须使用差分数组;有区间求和,必须使用树状数组
  • 原数组的区间和相当于先对差分数组1~i求和还原出原数组,再对原数组求和,O(n^2)一定会超时
  • 我们发现了对原数组1~n求和的公式(n+1)*(a1+…+an) - (1*a1+…n*an),因此我们可以构造两个树状数组分别维护ai与i*ai的差分

题解

#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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有