树状数组

解决的问题

  1. 基本问题:单点修改,区间查询
  2. 利用差分:区间修改,区间查询

总的来说就是:频繁修改+区间查询

与线段树区别

树状数组可以解决的问题都可以用线段树解决。

两者的区别
树状数组的优点:

  • 相比线段树系数系数要少很多
  • 容易写,代码量小

线段树的优点:

  • 可以解决复杂问题。

理解

image
image
image
image

代码

lowbit

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

更新/建立

一般来说建立就是直接将树状数组更新n次

 //x为更新的位置,y为更新后的数,n为数组最大值
void update(int x,int y,int n){
    for(int i=x;i<=n;i+=lowbit(i))
        c[i] += y;
}

查询

查询1~x之间的数的和。
很明显查询一段区间的就是sum(x)-sum(y)

int sum(int x){
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans += c[i];
    return ans;
}

应用

单点修改,区间查询

241. 楼兰图腾

思路:
对于某个点a[i]的V的数量就是:
a[i]前面大于a[i]的数的数量 × a[i]后面大于a[i]的数的数量
也同理,就是换成小于a[i]的数的 数量

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

typedef pair<int, int> pii;
const int N = 2000010;
const int INF = 0x3f3f3f3f3f;

int n;
int a[N];
int tr[N];
int upper[N],lower[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;
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i))
      res+=tr[i];
      return res;
}

void slove()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
   //统计i前面 大于和小于 i的数 的数量
    for(int i=1;i<=n;i++){
        int x=a[i];
        upper[i]=sum(n)-sum(x);
        lower[i]=sum(x-1);
        add(x,1);
    }

    memset(tr,0,sizeof tr);
    int ans1=0,ans2=0;
   //统计i后面 大于和小于 i的数 的数量,并求出答案
    for(int i=n;i>=1;i--){
        int y=a[i];
        ans1+=upper[i]*(sum(n)-sum(y));
        ans2+=lower[i]*sum(y-1);
        add(y,1);
    }
    printf("%lld %lld\n",ans1,ans2);

}

signed main()
{
  slove();
  return 0;
}

区间修改,单点查询

242. 一个简单的整数问题
题意:
有两类指令:

  1. 第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。
  2. 第二类指令形如 Q x,表示询问数列中第 x 个数的值。

思路: 裸题, 利用树状数组维护差分数组 即可,看代码吧

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

typedef pair<int, int> pii;
const int N = 1e5+10;
const int INF = 0x3f3f3f3f3f;

int n,m;
int a[N];
int tr[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;
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i))
      res+=tr[i];
      return res;
}

void slove()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }

    while(m--){
        string s;cin>>s;
        if(s=="Q"){
            int x;cin>>x;
            cout<<sum(x)+a[x]<<endl;
        }
        else{
            int l,r,d;
            cin>>l>>r>>d;
            add(l,d);
            add(r+1,-d);
       }
    }

}

signed main()
{
  slove();
  return 0;
}

区间修改,区间查询

维护两个树状数组即可:

  1. 一个是差分数组d[i]的树状数组tr[i]
  2. 一个是原始数组的差分数组乘以i即i*d[i]的树状数组tri[i]
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

typedef pair<int, int> pii;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f3f;

int n, m;
int a[N];
int tr[N];
int tr2[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;
        tr2[i] += x * c;
    }
}
int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += (x + 1) * tr[i] - tr2[i];
    return res;
}

void slove()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        add(i,a[i]-a[i-1]);
    }

    while (m--)
    {
        string s;
        cin >> s;
        if (s == "Q")
        {
            int l, r;
            cin >> l >> r;
            cout << sum(r) - sum(l - 1) << endl;
        }
        else
        {
            int l, r, d;
            cin >> l >> r >> d;
            add(l, d);
            add(r + 1, -d);
        }
    }
}

signed main()
{
    slove();
    return 0;
}
posted @ 2022-07-09 10:30  kingwzun  阅读(31)  评论(0编辑  收藏  举报