树状数组与其离散化

建议看新版(不看后悔系列)

树状数组的概念:
   树状数组(Fenwick Tree,又称二叉索引树)是一个查询和修改复杂度都为log(n)的数据结构,它是利用二进制的一些特点来实现。它的功能有局限性,主要是用来动态查询连续和(或者是前缀和)的问题。它利用O(n)的附加空间复杂度,将线性的数列结构转化成树状结构从而进行跨越扫描,高效完成查询连续和。

原数组为a[ ],用树状数组储存后的数组为 C[ ]

来一张百度百科里的图片先找一下规律体会一下

模板函数:
 

int lowbit(int x)//函数作用:获取x二进制中从右往左数第一个1所代表的的十进制数。//比如1110,变为0010,值为2
{
    return x&(-x);//x&(x^(x-1))
}

上述函数作用:x节点最近的父亲的编号=x+lowbit(x)。x最近前驱的编号=x-lowbit(x);//当我们求1->x的和时,C[x]如果包含的不是1-->x的全部和时(客观规律是只有x为2^k(k=0,2,3.....)时,C[x]才包含前x项的和),比如求1到6的和sum(6),肯定有C[6],C[6]=a[6]+C[5],还需要谁?由上图易得还需要C[4]。4怎么来的呢?4=6-lowbit(6);而4-lowbit(4)=0表示已经得到所有和了(4为2^2,C[4]表示之前所有和)……这种点不妨称之为x的前驱节点(可能有若干个)

固得sum(6)=C[6]+C[4].于是就延伸出下述模板

模板函数



int ask(int x)//函数作用:获取1->x的和
{
    int sum=0;
    for(;x>0;x-=lowbit(x))
    {
        sum+=C[x];
    }
    return sum;
}

模板函数


//函数作用:由于某个值改变,需要更新树状数组
void add(int x,int num)//在x处增加num
{
    for(;x<=N;x+=lowbit(x))//N为树状数组的上界
      C[x]+=num;
}

模板函数

//树状数组初始化
//1、

for(int i=1;i<=n;++i)//函数作用,将a[]-->C[]
        {
            scanf("%d",&a[i]);
            if(i&1) C[i]=a[i];//客观规律吧,写写就明白了
            else {
                    C[i]=a[i];
                int f=lowbit(i);
                f>>=1;
                while(f){
                    C[i]+=C[i-f];
                 f>>=1;
                }
            }
        }

//上述是我发现的一个规律,其实是麻烦了,但感觉上面比下面的高效吧

2、
//其实只要add(i,a[i])就是把a[]->C[],是我理解的不够

for(int i=1;i<=n;++i)
{

   add(i,a[i]);

}


//3、
void init(){//线性构造
    for (int i = 1; i <= n; i++){
        pre[i] = pre[i - 1] + a[i];//前缀和
        c[i] = pre[i] - pre[i - lowbit(i)];
    }
}

最后是一道模板题体会一下      树状数组模板题

PLUS

   求区间最大值

a[ ]原数组的值,C[ ]区间最大值

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

 

void add(int x,int num)
{
    a[x]=num;
	int idx;
	for(;x<=n;x+=lowbit(x))
	{
		C[x] = a[x];
		idx = lowbit(x);
		for (int i=1; i<idx; i<<=1)
			h[x] = max(h[x], h[x-i]);
	}		
}

 

int getmax(int x, int y)
{	
    int ans = 0;
    while (y >= x)
    {
      ans = max(a[y], ans);
      y --;
      for (; y-lowbit(y) >= x; y -= lowbit(y))
      ans = max(h[y], ans);	
      }
      return ans;
 }

离散化:

关于离散化,这篇文章讲的很好——>传送

模板:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100000;
int a[maxn];
int b[maxn];
int n;
int main()
{
    int m=0;
    cin>>n;//元素个数
    for(int i=1;i<=n;++i)
    {
    scanf("%d",&a[i]);
    b[i]=a[i];//b[]作为离散化的数组
    }
    sort(b+1,b+1+n);//将b数组排序,因为是从b[1]开始存储的,所以要b+1
    m=unique(b+1,b+1+n)-b-1;//去重操作,返回不同元素的个数
    for(int i=1;i<=n;++i)
      a[i] = lower_bound(b+1,b+1+m,a[i]) - b;
    return 0;
}

根据原值查询(映射)

//原arr数组存值
 memcpy(num,arr,sizeof(arr));
     sort(num+1,num+cnt+1);
     int Size= unique(num+1,num+cnt+1) - num-1;


int query(int x,int Size)
{
    return lower_bound(num+1,num+Size+1,x) - num;
}

树状数组的本质操作就是维护区间的前缀和

1、区间修改,单点查询————树状数组维护差分数组

树状数组只能查询前缀和 和 单点修改,我们可以通过设立一个差分数组d[ ],并用树状数组维护,那么区间修改就变为了单点修改。如在[l,r]的区间内增加一个k,那么add(l,k),add(r+1,-k)。这就完成了差分数组的维护,若单点查询x处 的值,只需a[x] + ask(x)。

2、区间修改,区间查询————两个树状数组维护(可以用来完成动态区间 加 等差数列的维护)

由上面我们得知,用树状数组进行区间修改就是维护一个差分数组d,对于x位置处增加的值就是ask(x),即\sum_{i=1}^{x}b[i],那么对于原数组a[ ],数组a的前缀和S(x) 总体增加的值就是\sum_{i=1}^{x}\sum_{j=1}^{i}b[i]

\sum_{i=1}^{x}\sum_{j=1}^{i}b[i]=\sum_{i=1}^{x}(x-i+1)*b[i]=(x+1)\cdot \sum_{i=1}^{x}b[i]-\sum_{i=1}^{x}i\cdot b[i] 

即我们可以用两个树状数组维护上述两个前缀和。。(其中前一个是差分数组,后面是i*差分数组)

具体的说,我们建立两个树状数组c0,c1,起初全部赋值为0.对于每条指令 C l r d  执行四个操作

1、在树状数组c0中把位置 l 上的加上d

2、在树状数组c0中,把位置r+1 上的位置减去d

3、在树状数组c1中,在位置l 上加上l * d

4、在树状数组c1中,在位置r+1上的数减去(r+1)*d

另外,我们建立数组S存储a的原始前缀和。所以对于每条指令 Q l r为:

[S(r)+(r+1)*ask(c0,r)-ask(c1,r)]-[S(l-1)+l*ask(c0,l-1)-ask(c1,l-1)]

虽然这样区间修改区间维护这个做法比较麻烦,不如直接用线段树,不过,有时候动态区间加等差数列会产生比较巧妙的用处。

附上算法竞赛进阶指南上的代码

const int SIZE = 100010;
int a[SIZE],n,m;
ll c[2][SIZE],S[SIZE];
int lowbit(int x){return  x&(-x);}
ll ask(int k,int x){
    ll ans = 0;
    for(;x > 0;x -= lowbit(x)) ans += c[k][x];
    return ans;
}
void add(int k,int x,int y){
    for(;x <= n;x += lowbit(x)) c[k][x] += y;
}
int main(){
    cin >> n >> m;
    for(int i = 1;i <= n;++i) scanf("%d",&a[i]),S[i] = S[i-1] + a[i];
    while(m --){
        char op[3];int l,r,d;
        scanf("%s%d%d",op,&l,&r);
        if(op[0] == 'C'){
            scanf("%d",&d);
            add(0,l,d);add(0,r+1,-d);
            add(1,l,l*d);add(1,r+1,-(r+1)*d);
        }
        else {
            ll ans = S[r] + (r+1)*ask(0,r) - ask(1,r);
            ans -= S[l-1] + l *ask(0,l-1) - ask(1,l-1);
            printf("%lld\n",ans);
        }
    }
}

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vue树状数组扁平化是一个操作,用于将树状数组(也称为B树或二叉搜索树)中的数据结构扁平化为线性数据结构。这个操作通常用于数据展示和性能优化。 在Vue树状数组扁平化过程中,需要遍历树状数组,并将每个节点转换为对应的线性数据结构元素。这通常可以通过递归实现。 下面是一个简单的Vue树状数组扁平化的实现步骤: 1. 定义一个数据结构来表示树状数组中的节点。每个节点包含一个数据值和指向其子节点的指针。 2. 定义一个递归函数,用于遍历树状数组并转换每个节点。该函数需要接受当前节点的值和指针,以及一个空列表(用于存储扁平化的结果)。 3. 在函数中,检查当前节点是否有子节点。如果有,递归调用函数自己处理子节点。否则,将当前节点的值添加到结果列表中。 4. 最后,遍历完成后,将结果列表转换为一个普通的数组或其他适当的线性数据结构,并将其传递给Vue组件进行展示。 需要注意的是,Vue树状数组扁平化操作可能会对性能产生一定的影响,特别是在处理大型数据集时。因此,在实现过程中需要考虑性能优化措施,例如使用分页或懒加载等技术来减少不必要的计算和渲染。 此外,为了更好地使用Vue树状数组扁平化,你可能需要使用Vue的数据绑定机制和组件系统来动态地呈现扁平化的数据结构。你可以创建一个组件来显示扁平化的数据,并使用Vue的数据绑定语法将其与数据源进行绑定。 总之,Vue树状数组扁平化是一个常用的操作,可以帮助你更有效地处理树状数组数据,并提高性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值