权值树状数组的简单介绍

什么是权值数组:

for i =1 to n do ++A[a[i]]

也就是说,权值数组的A[i]存储的是给定序列a[1]-a[n]中等于i的元素个数。

权值数组的前缀和:

for i = minval+1 to maxval do A[i]+=A[i-1]
minval=min{a[i]} 
maxval=max{a[i]}

也就是说,权值数组的前缀和A[i]就表示原序列a[1]-a[n]中小于等于i的元素个数。

权值树状数组:

权值树状数组的操作:

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

int getsum(int v)//小于等于v的元素的数目
{
	int s=0;
	for(;v;v-=lowbit(v))
		s+=cnt[v];
	return s;
}

void update(int v,int num)//将值为v的元素增加num个
{
	for(;v<=maxn;v+=lowbit(v))
		cnt[v]+=num;
}

根据前面的了解,我们知道getsum(v)得到的是原序列中小于等于v的元素的个数,update(int v,int num)是在原序列中插入num个值为v的元素。

有什么用呢:

权值数组前缀和是单调递增的,那么权值树状数组自然也是单调递增的,利用这一点我们可以二分查询原序列中第k大的值。拿getsum(mid)的值跟k值相比来缩小上下界就可以做到这一点。时间复杂度O((lgn)^2)。

while(l<=r)	
{
	int mid=(l+r)>>1;
	int t=getsum(mid);
	if(t<k)
		l=mid+1;
	else
		r=mid-1;
}
printf("%d\n",l);

还有一种时间复杂度为O(lgn)的方法,比较玄学,我大概解释一下吧。首先我们假设第k大的数为temp,那么getsum(temp)=k,那么如果我们能找到满足getsum(i)<k的最大的i,那么i必定为temp-1,即第k大的数等于i+1。这就是这个函数的核心思想。再深入一点,i又可以写成二进制的形式,假设i=2^n+2^(n-1)+……+2^t+……+2^0,那么求getsum(i)的过程就是求tree[i]+tree[i-2^0]+tree[i-2^0-……-2^t]+tree[i-2^0-……-2^t-……-2^(n-1)],也即求tree[2^n]+tree[2^n+2^(n-1)]+……+tree[i],我们发现,只要从二进制的高位向低位扩展,求i的过程就可以顺便求出getsum(i),从而降低时间复杂度。

int find_kth(int k)
{
	int ans=0,sum=0,i;
	for(int i=20;i>=0;i--)
	{
		ans+=(1<<i);
		if(ans>=maxn||sum+cnt[ans]>=k)
			ans-=(1<<i);
		else
			sum+=cnt[ans];
	}
	return ans+1;
}

 

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值