一道树状数组的离线询问题
下面简单介绍一下树状数组
如果已经知道树状数组是什么的可以直接跳到分割线后面


树状数组他其实利用的是类似于二进制的东西,所以英文名也叫做BIT(Binary Index Tree)每一次通过lowbit函数求出来当前这个数的所管范围,假设现在的数是x,他的区间就是[x-lowbit(x)+1,x],那么我们为什么要用这个树状数组呢?或者说它有什么好处吗?
其实树状数组它能解决的问题十分有限,相比较线段树这个数据结构,树状数组只能够处理求区间和的问题,但是不能求解区间的最大值,最小值,异或值等等。那么树状数组和普通的数组查询和修改又有什么不一样的?
很简单,树状数组由于使用的是二进制的原理,那么他每一次的修改操作,查询操作就一定是妥妥的O(nlogn).肯定好于一般的操作了。
下面放一个从百度上面找到的很经典的树状数组的图方便大家理解:
图片说明


其实树状数组的题目难就难在你要发现它可以使用树状数组来做,其实代码并不难,相比线段树还是要好写一些的。言归正传,这个题要怎么做呢?
我们可以构建一个结构体存储每一次的查询的范围len,查询的编号id,查询的数值x,以及要设置一个flag来代表是左端点还是右端点。因为你如果是左端点的话,你就要减去1,这一点相信大家都知道,因为是前缀和的原理了,就是你要求l到r的和你只需要做sum(r)-sum(l-1)这样就行了。所以如果在设置左端点的时候一定要范围-1。
很关键的一步是要按照每一个结构体的排个序,这样可以保证从前往后扫一遍,不重不漏
,并且使线性的复杂度,整个程序的时间复杂度大概是O((n+m)logn)
之后再从前往后每一次去加入a[i];进行add(a[i],1)这个操作,每一次求出来这个点之前的所有满足大于x的sum值,就可以了。
详情见代码~

code

#include<bits/stdc++.h>
using namespace std;
int a[100010],n,m;
int l[100010],r[100010],tr[100010];
struct node{
    int len,id,val,flag;
}p[200010];//这里要特别注意要开两倍,不然会数组越界
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int c)
{
    for(int i=x;i<=100000;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x)
{
    if(x==0) return 0;
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    int now=1;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        p[now++]={x-1,i,z,0};//构建左边的查询点
        p[now++]={y,i,z,1};//构造右边的查询点
}
    sort(p+1,p+1+2*m,[](node a,node b){return a.len<b.len;});//排序比较
    now=1;
    for(int i=0;i<=n;i++)
    {
     if(a[i]!=0)
        add(a[i],1);
        while(p[now].len==i&&now<=2*m){//当这个查询区间的长度为当前的i时候,可以计算得出结果

            if(p[now].flag==1)r[p[now].id]=sum(p[now].val);
                else l[p[now].id]=sum(p[now].val);
            ++now;
        }
    }
    for(int i=1;i<=m;i++)
    {
        cout<<r[i]-l[i]<<endl;//对于每一个区间输出结果
}
    return 0;
}