树状数组及扩展

树状数组:顾名思义,就是用数组来模拟树形结构。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。

树状数组能解决什么问题:可以解决大部分基于区间上的更新以及求和问题。

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

如果用普通的数组存每个数 1操作就是O(n) 2操作是O(1)的

如果维护前缀和数组 那么1操作是O(1) 2操作是O(n) 

扩展:联系差分和推公式

差分的扩展:树状数组原来的操作1.快速求前缀和O(logn)  2.修改某一个数O(logn)

现在反过来1.修改某一段区间 2.求某个值 这就联系差分了

与线段树的联系:树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟。(打小兵用大刀)

详情

基本原理:

首先先定义a[i]为每个元素的值

然后假定我们要求的是1~x的总和 也就是前x个数的和

k>k-1>k-2>......>i

将x用二进制表示 x就可以等于2^k+2^(k-1)+2^(k-2)+.....+2^i 那么我们是不是可以求出前2^k个数 然后再去掉2^k个数 再取去掉2^k个数的前2^(k-1)个数 一直循环

example: ()为多少进制 

12(10)=1100(2)=1000(2)+100(2)=8(10)+4(10)12(10)=1100(2)=1000(2)+100(2)=8(10)+4(10)

是不是先取了前8个数(1-8) 再去除前八个数 选前四个数(9-12)

那么我们现在将前x个元素分为很多个区间去求和

1.( x-2^i ,x] 里面有 2^i 个元素

2.(x-2^(i)-2^(i+1),x-2^(i) ] 里面有2^(i+1)个元素

....

3.( 0 , x - 2^i - 2^(i+1)- ..... - 2^(k-1) ] 里面有 2^(k)个元素

然后可以发现区间( L,R ] 的长度一定是R二进制表示的最后一位 1

这时候我们就可以定义一个C数组

C数组的定义是  以 x 为右端点的长度为lowbit(x)的区间等价于以x结尾 长度为2^k( 末尾有k个0等价于lowbit(x) )的区间和

那么C[x] = a[ x-lowbit(x)+1 到 x ]的和

然后这段区间和一定包含 a[ x ] 那么 C[x] = a[x] +a[x - lowbit(x) +1 到 x-1 ]的和

由于x的形式是......01000这种形式 那么x-1就是......00111

那么那么我们现在就可以继续分段了

1. (....00110 到 ...00111] 这个是C[x-1]

2. (....00100 到 ...00110] 这个是C[(x-1)-lowbit(x-1)]

3. (....00000 到 ...00100]  这个是C[(x-1)-lowbit(x-1)-lowbit[(x-1)-lowbit[x-1]]]

那么把这几个全部加起来就是C[x]了

那么更新操作怎么办呢?

我要修改a[x] 那么我就得修改包含x的c数组

假设x为......01100....那么父节点就是......10000 那么修改就是每次加lowbit


1. 楼兰图腾

241. 楼兰图腾 - AcWing题库

tr表示这个位置有多少个这个数

#include <bits/stdc++.h>
using namespace std;
const int N=200010;
int n,m;
int tr[N];
int a[N];
int great[N],low[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;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int y=a[i];
        great[i]=sum(n)-sum(y);
        low[i]=sum(y-1);
        add(y,1);
    }
    memset(tr,0,sizeof tr);
    long long res1=0,res2=0;
    for(int i=n;i;i--)
    {
        int y=a[i];
        res1+=great[i]*(long long)(sum(n)-sum(y));
        res2+=low[i]*(long long)(sum(y-1));
        add(y,1);
    }
    printf("%lld %lld\n",res1,res2);
    return 0;
}

2.一个简单的整数问题(差分)

242. 一个简单的整数问题 - AcWing题库

把tr数组当成差分数组即可

假如[l,r]+c,则b[l]+=c,b[r+1]-=c;

假如求x的数是多少,相当于求b[x]的前缀和,然后可以直接用树状数组求用sum(x)即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010;
int n,m;
int a[N];
ll 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;
}
ll sum(int x)
{
    ll res=0;
    for(int i=x;i;i-=lowbit(i))
    {
        res+=tr[i];
    }
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        add(i,a[i]-a[i-1]);
    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s%d",op,&l);
        if(op[0]=='C')
        {
            scanf("%d%d",&r,&d);
            add(l,d),add(r+1,-d);
        }
        else
        {
            printf("%lld\n",sum(l));
        }
    }
    return 0;
}

3.一个简单的整数问题2(差分+分析)

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

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
int n,m;
ll tr1[N];
ll tr2[N];
int a[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+=tr[i];
    }
    return res;
}
ll prefix_sum(int x)
{
    return sum(tr1,x)*(x+1)-sum(tr2,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        add(tr1,i,a[i]-a[i-1]);
        add(tr2,i,(ll)(a[i]-a[i-1])*i);
    }
    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s%d%d",op,&l,&r);
        if(op[0]=='Q')
        {
            printf("%lld\n",prefix_sum(r)-prefix_sum(l-1));
        }
        else
        {
            scanf("%d",&d);
            add(tr1,l,d),add(tr2,l,l*d);
            add(tr1,r+1,-d),add(tr2,r+1,(r+1)*(-d));
        }
    }
    return 0;
}

4.谜一样的牛(二分查找)

244. 谜一样的牛 - AcWing题库

思路:从n往1看,每次给剩余的数里面第a[i]+1小的数,然后把这个数删掉,一直这样操作即可

找第a[i]+1小的数:

1.把每个数初始值为1,表明这个数还没用过,每个前缀和表明了当前数是第几小的数

2.二分查找第a[i]+1小的数,因为每个数的前缀和是递增的可以二分

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N],tr[N],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;
}
int sum(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) add(i,1);//初始化初始值,让每个数都为1,表明没有用过
    for(int i=n;i;i--)
    {
        int b=a[i]+1;//找第a[i]+1小的数
        int l=1,r=n;
        while(l<r)//二分
        {
            int mid=l+r>>1;
            if(sum(mid)>=b) r=mid;//假如前缀和大了,说明当前数不是第b小的数
            else l=mid+1;
        }
        ans[i]=l;//让答案等于当前找到的第b小的数
        add(l,-1);//把当前数删除,因为已经用过了
    }
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值