码迷,mamicode.com
首页 > 编程语言 > 详细

树状数组BIT

时间:2020-04-19 17:39:55      阅读:69      评论:0      收藏:0      [点我收藏+]

标签:span   signed   适合   最大   记录   string   位置   区间   log   

树状数组是利用数的二进制特征进行检索的树状结构

一般只适合对点进行更新O(logN),对区间进行查询O(logN)

对于源数据a[],c[]表示的时a[n-2^k+1]+a[n-2^k+2]+.....+a[n]的和,其中k为n在二进制下末尾0的个数,c[i]的覆盖范围长度时lowbit(i)

即i号位之前lowbit(i)位数的和 ,2^k=n and (n xor(n-1)) ,lowbit运算时根据n的值返回2^k

操作

(1)修改元素add(k,x),把ak加上x

(2)求和sum(x),sum(x)=x1+x2+...+xx,然后ai+...+aj=sum(j)-sum(i-1)

重要操作:lowbit(x)   x&(-x),这个操作时找到x的二进制数的最后一个1,原理是利用了负数的补码表示,补码是原码取反加1

(1)#define lowbit(x) ((x)&(-x))       lowbit(x)也可以理解为能够整除x的最大2的幂次!

(2)int lowbit(int x){

        return x&(x^(x-1));

        }

令m=lowbit(x),定义tree[x]的值,是把ax和它前面的共m个数相加的结果

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

//查询,查询一个区间的和
//sum[1,x]=sum(1,x-lowbit(x))+c[x]
int summ(int x){
	int summ=0;
	while(x>0){
		sum+=c[x];
		x-=lowbit(x);
	}
} 
//更新,对一个数据进行更改
void upda(int index,int num){
	while(index<=n){
		c[index]+=num;
		index+=lowbit(index);
	}
} 

//二维的a[n][m]
//更新:给节点a[i][j]加上k
void upda(int i,int j,int k){
	while(i<=n){
		int temp=j;
		while(temp<=n){
			c[i][temp]+=k;
			temp+=lowbit(temp);
		}
		i+=lowbit(i);
	}
} 
//查询:
//a[1][1]~a[i][j]的和
int sum(int i,int j){
	int summ=0;
	while(i>0){
		int temp=j;
		while(temp>0){
			summ+=c[i][temp];
			temp-=lowbit(temp);
		}
		i-=lowbit(i);
	}
	return summ;
} 

应用

1:给出一个序列,和一系列查询,每次查询前i个数的总和,但是同时会在第x个数上增加v,这样每次查询的结果都会变化(在线查询)

树状数组:lowbit[]数组,记录在i号位之前(包含i号)lowbit(i)个数的和,(ps;下标必须从1开始)

int c[maxn];    //记录在i号位之前(包含i号)lowbit(i)个数的和,
int getsum(int k){ //返回前k位数据之和 
	int sum=0;
	for(int i=k;i>0;i-=lowbit(i)){
		sum+=c[i]; //c[i]记录的本来就输若干个a[i]的和,所以可直接累加 
	}
	return sum; 
}
void update(int x,int v){ //在第x个数据上加上v 
	for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v; 
	//在每个包含了数据x(下标)的块里面都加上v 
}

最经典应用:给定一个有n个正整数的序列A,对序列中的每个数,求出它左边比他小的数的个数
只需要把update的含义改变一下即可:x出现的次数加1
eg:如果统计元素右边比它大的数据个数:getsum(n)-getsum(a[i])即可
eg:如果统计元素左边比它大的数据个数(右边比它小):从右往左遍历即可

void js(){
	int n,d;
	cin>>n;
	memset(c,0,sizeof(c));
	for(int i=1;i<=n;i++){
		cin>>d;
		update(d,1,n); //d出现的次数加1
		cout<<getsum(d-1);//查询当前小于d的数!!!!!我居然写在另一个循环了!!!我有病!!!
		                   //既然是在线查询,就不能在全部处理完了在计算!!!!! 
	} 
	cout<<endl;
} 

特殊情况下改进:
A[i]<N,即A[i]很大的情况下:eg:{520,999999,18,8888}等价于{2,4,1,3}
离散化:把任何不在合适区间的整数或者非整数都转换为不超过元素个数大小的整数
跟计算排名问题相似:根据值从小到大排名,如果成绩一样,排名也一样
利用结构体存储原本的数据和原始下标,再用另一个数组存储他们的排名,在这个数组的基础上计算

struct node{
	int val;   //会超过存储大小的实际的值 
	int index;  //原本的下标 
}temp[maxn]; 
int a[maxn];
int cc[maxn]; //记录在i号位之前(包含i号)lowbit(i)个数的和
bool cmp(node a,node b){
	return a.val<b.val;
}
void js2(){
	int n;cin>>n;
	memset(cc,0,sizeof(cc));
	for(int i=1;i<=n;i++){
		cin>>temp[i].val;
		temp[i].index=i;
	}
	sort(temp+1,temp+n+1,cmp);
	//计算排名了!!记住了啊!! 
	for(int i=1;i<=n;i++){
	   if(i==1||temp[i-1].val!=temp[i].val) a[temp[i].index]=i+1; //与前一个相比值不同,排名就要加1  这个下标值现在的数据 
	   else a[temp[i].index]=a[temp[i-1].index]; //否则排名就是一样滴~ 
	}
	//完了就是开始更新和求值了
	for(int i=1;i<n;i++){
		update(a[i],1,n);
		cout<<getsum(a[i]-1)<<" ";
	} 
	cout<<endl;
}

  

例题:POJ 2182 Lost Cows

题目大意:有n头牛,给出从第二头牛开始每头牛前面有多少头牛编号比自己小。

求牛的编号。

分析:每次最后一头牛的编号为当前可用编号中排在第a[i]+1的数。

这道题的特殊在于:1、不需要用add()初始化,因为lowbit(i)就是tree[i]    2、对每个pre[i]+1,用findpos()二分找出sum(x)=pre[i]+1所对应的x,就是第x头牛

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=10010;
const int INF=0x3fffffff;
typedef long long LL;
#define lowbit(x) ((x)&(-x))
int tree[maxn],pre[maxn],ans[maxn];
int n;
void add(int x,int d){  //更新数组tree[] 
	while(x<=n){
		tree[x]+=d;
		x+=lowbit(x);
	}
} 
//求和
int summ(int x){
	int ans=0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
} 
int findpos(int x){   //寻找sum(x)=pre[i]+1所对应的x,就是第x头牛
	int l=1,r=n;
	while(l<r){
		int mid=(l+r)>>1;
		if(summ(mid)<x) l=mid+1;
		else r=mid;
	} 
	return l;
}
int main(){
	scanf("%d",&n);
	pre[1]=0;
	for(int i=2;i<=n;i++){
		scanf("%d",&pre[i]);
	} 
	for(int i=1;i<=n;i++){
		tree[i]=lowbit(i);  //这个题目特殊,不需要add初始化,直接用lowbit就可以了 
	}
	for(int i=n;i>=1;i--){
		int x=findpos(pre[i]+1);
		add(x,-1);  //更新tree数字,减少一个
		ans[i]=x; 
	} 
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}

  这道题的线段树做法

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//线段树做法
/*
从后往前遍历输入的序列,遇到的每个值a表示此牛在剩余牛中排在第a+1个,删除此编号,循环此过程,最终得到的序列即为牛在此队列中的编号序列。
借助线段树查找未删除的数中排在第a+1个位置(编号排序位置)的牛的位置(读取顺序)
*/ 
struct node{
	int l,r,len;
}cow[100000];
int s[100000],ans[100000];
void build(int v,int l,int r){
	cow[v].l=l;
	cow[v].r=r;
	cow[v].len=r-l+1;
	if(l==r) return;
	int mid=(l+r)/2;
	build(v*2,l,mid);
	build(v*2+1,mid+1,r);
}
int que(int v,int k){
	--cow[v].len;
	if(cow[v].l==cow[v].r) return cow[v].r;
	//找到叶子节点, 注意此处不可用cow[v].len == 0代替,否则单支情况将直接返回,导致未达到最末端
	else if(cow[v*2].len>=k){
		return que(v*2,k);
	}
	else return que(v*2+1,k-cow[v*2].len);
}
int main(){
	int n;
	while(~scanf("%d",&n)){
		for(int i=2;i<=n;i++) scanf("%d",&s[i]);
		s[1]=0;
		build(1,1,n);
		for(int i=n;i>=1;i--){
			ans[i]=que(1,s[i]+1); 
		} 
		for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
	}
return 0;
}

  

 

树状数组BIT

标签:span   signed   适合   最大   记录   string   位置   区间   log   

原文地址:https://www.cnblogs.com/shirlybaby/p/12732241.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!