树状数组总结



树状数组总结

树状数组是一个类似于线段树的树状结构,它通过存储一定区间内的元素来达到快速插入、快速求和的效果。并且可以节省时间,节省空间,减少代码量,可谓是非常优秀的一个树形结构

方格中数字代表对应数组的第几个元素,下排是a数组,其上方的是e数组,最下的二进制则是对应编号的二进制表示.

可以观察到,树状数组把一个数组内的元素进行了一定的二分,并存储某些元素的和,使树的深度达到了logn,这样不论是对于查找还是插入都进行了极大的优化

      每个数所指向的元素个数为1+它的二进制末尾0的个数我们把二进制中最后一个一出现的位置叫做lowbit,每个元素指向的下一个元素为x+lowbit(x),现在要修改整棵树就比较容易了。

 

  1. 累加操作

void add(int x,int t)

 {

       while(x<=n){

              e[x]+=t;

              x=x+lowbit(x);

       }

 }

 

  1. 求和操作

    int sum(int x)
    
     {
    
           int s=0;
    
           while(x>0)
    
           {
    
                  s+=e[x];
    
                  x-=lowbit(x);
    
           }
    
           return s;
    
     }
    
    
    
    

     

其中,求和操作求的是1~x个元素之和,要求a~b个元素之和,只需:

Ans=sum(b)-sum(a-1);

其中运用了两次查询,对于查询单个元素,还可以优化为一次:

借助data[x]=e[x]-(query(x-1)-query(LCA(x,x-1)))

int sum(x){

       int ans=e[x];

       int lca=x-lowbit(x);//求最近公共祖先 

       x--;

       while(x!=lca){

              ans-=e[x];

              x-=e[x];

       }

}



 

拓展操作:

  1. 区间修改、点查询

e[i]i元素与i-1元素的差值

那么区间[i,j]修改:

e[i]+=d; e[j+1]-=d;

 

单个点的求值:data[i]=e[1]+e[2]+…+e[i];

 

  1. 找第k小的元素

e[i]数组存取i出现的次数(有时数据太大可以离散优化)

 

问题可以转化为求区间和为k所对应的下标

区间和为递增区间,用二分查找

 

4 6 5 5 2 2 3 1,每个数作为下标,add一次

然后sum(i)求出的就是当前1~i区间的数字个数,并且数字有序,二分即可

 

 

  1. 向高维拓展

树状数组与线段树相比,还是比较方便向高维拓展的,只需加一重循环即可

void Change(int x,int y,int data){

       While(x<=n){

              int ty=y;

              While(ty<=n){

                     e[x][ty]+=delta;

                     ty+=lowbit(y);

              }

              x+=lowbit(x);

       }

}




树状数组


Ultra-QuickSort


题目(poj 2299


请分析,对于一串数列,各个数字不相同,至少要交换多少次才能使该数列有序(从小到大)?


输入:


输入包含多组测试样例,每组测试样例都是先输入一个整数n<500000——序列的长度,以下n行每行包括一个整数 0 a[i] 999,999,999,已输入的n0作为输入的终止条件。


输出:


对于每一组样例,输出一个整数,表示至少要交换的次数。


输入样例:


5


9


1


0


5


4


3


1


2


3


0


输出样例:


6


0


 


很标准的一道题,先将所有元素取出排序,去重,作为离散优化二分的依据


依次从第一位开始向树状数组对应位置加值,add(find(x),1),并且同时算出当前比该元素大的值有多少,更新答案


 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 500020
using namespace std;
int n,m,p,t[MAX],data[MAX];
long long e[MAX],s,ans;
int lowbit(int i){
	return -i&i;
}
void add(int x,int p){
	for(;x<=n;x+=lowbit(x))
		e[x]+=p;
}

long long sum(int x){
	for(s=0;x>0;x-=lowbit(x))
		s+=e[x];
	return s;
}

int Find(int s,int e,int aim){
	while(s<=e){
		int m=(s+e)>>1;
		if(t[m]==aim) return m;
		if(t[m]>aim) e=m-1;
		else s=m+1;
	}
}

int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)&&n){
		memset(e,0,sizeof(e));
		ans=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&data[i]);
			t[i]=data[i];
		}
		sort(t+1,t+n+1);
		m=unique(t+1,t+n)-t;
		for(int i=1;i<=n;i++){
			int f=Find(1,m,data[i]);//找离散优化对应元素 
			ans+=sum(n)-sum(f);//每次看比此数大的数有几个 
			add(f,1);//将此数加入 
		}
		printf("%lld\n",ans);
	}
	return 0;
}


 


Poj3928 Ping Pong


题意:


n3 < = 20000)乒乓球运动员住在一个西大街(考虑街道作为一个线段)。每个球员都有一个独特的技能等级。为了提高他们的技能等级,他们经常互相竞争。如果两个球员想参加比赛,他们必须选择其他乒乓球运动员的裁判,并在裁判的房子举行比赛。因为某些原因,参赛者不能选择一个技能等级较高或低于他们两个级别的裁判。参赛者必须走到裁判的房子,因为他们是懒惰的,他们想使他们的总步行距离不超过他们的房子之间的距离。当然,所有的球员都住在不同的房子,他们的房子的位置都是不同的。如果裁判或任何两名选手是不同的,我们称之为两个不同的游戏。现在的问题是:有多少不同的游戏可以在这个乒乓街举行?


输入


The first lineof the input contains an integer T(1<=T<=20), indicating the number oftest cases, followed by T lines each of which describes a test case.


输入的第一行包含一个整数t1 < =T < = 20),表示测试用例的数量,然后是T行,其中每一个描述了一个测试用例。


Every testcase consists of N + 1 integers. The first integer is N, the number of players.Then N distinct integers a1, a2 ... aN follow, indicating the skill rank ofeach player, in the order of west to east. (1 <= ai <= 100000, i = 1 ...N).


每一个测试用例都是由n1个整数组成的。第一个整数是n,玩家的数量。然后,n个不同的整数A1A2…一个跟随,指示每个玩家的技能等级,在西到东的顺序。(1 < = 100000I = 1…n)。


Output


输出


For each testcase, output a single line contains an integer, the total number of differentgames.


对于每一个测试用例,输出一个单行包含一个整数,不同游戏的总数。


先把所有选手的能力值排序,然后加入能力值最低的选手,然后枚举所有可能成为裁判的选手,同时计算出这个裁判的左边和右边比裁判能力值大和小的值,ans+=(lmin*rmax+lmax*rmin);


 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 20020
using namespace std;
struct node{
	int id,data;
}peo[MAX];
long long e[MAX],t[MAX],data[MAX],lmin,rmin,lmax,rmax,ans;
int n;

bool cmp(node a,node b){
	return a.data<b.data;
}
int lowbit(int i){
	return -i&i;
}
void add(int x,int t){
	for(;x<=n;x+=lowbit(x))
		e[x]+=t;
}
long long sum(int x){
	long long s=0;
	for(;x>0;x-=lowbit(x))
		s+=e[x];
	return s;
}
int main(){
//	freopen("in.txt","r",stdin);
	int cas;
	scanf("%d",&cas);
	while(cas--){
		scanf("%d",&n); ans=0;
		memset(e,0,sizeof(e));
		for(int i=1;i<=n;i++){
			scanf("%d",&peo[i].data);
			peo[i].id=i;//位置 
		}
		sort(peo+1,peo+1+n,cmp);//按能力排序 
		add(peo[1].id,1);//实力最小的选手 
		for(int i=2;i<=n-1;i++){
			lmin=sum(peo[i].id-1);
			lmax=peo[i].id-lmin-1;
			rmin=sum(n)-sum(peo[i].id);
			rmax=n-peo[i].id-rmin;
			ans+=(lmin*rmax+lmax*rmin);
			add(peo[i].id,1);
		}
		printf("%lld\n",ans);
		
	}
	
	return 0;
}

 


 


Stars


题目(POJ 2352


天文学家常常检查星星地图,星星都有它的x,y坐标,星星的等级的是由下边星星数量和星星右边的星星数量决定。



例如,看看上面的星图。星星5的等级为3 (由星星124决定的)。星星2的等级为1(由星星1决定的)。在这张地图上0级的星星有一颗,1级的星星有两颗,2级的星星有一颗,3级的星星有一颗,


 


你要编写一个程序,计算每个等级的星星的数量。


 


输入:


第一行为星星的数量N(1<=N<=15000)


接下来N行,每行为星星的x,y坐标,用空格来分开(0<=X,Y<=32000),每一个点上只有一个星星,按Y的升序给出坐标,如果Y相同,则按照X的升序给出。


输出:


输出应该包含N,每行一个数字。第一行0级星星的数量,第二行1级星星的数量等等,最后一行包含n – 1星星的数量。


输入样例:


5


1 1


5 1


7 1


3 3


5 5


输出样例:


1


2


1


1


0


 


乍一看像一道二位树状数组题,其实是一维的,因为数据本来就是按y的大小排序,添加每个点x之前先计算sum(x),这就是星星的等级,加入答案,继续直到循环结束

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 32020
using namespace std;
int e[MAX],cnt[MAX],n;

int lowbit(int i){
	return -i&i;
}
void add(int x,int t){
	for(;x<=MAX;x+=lowbit(x))
		e[x]+=t;
}
int sum(int x){
	int s=0;
	for(;x>0;x-=lowbit(x))
		s+=e[x];
	return s;
}
int main(){
//	freopen("in.txt","r",stdin);
	int x,y;
	while(~scanf("%d",&n)){
		memset(e,0,sizeof(e));
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++){
			scanf("%d%d",&x,&y);
			add(x+1,1);
			cnt[sum(x+1)]++;
		}
		for(int i=1;i<=n;i++)
			printf("%d\n",cnt[i]);
	}
	
	
	return 0;
}

Hdu 4031 attack

问题描述

今年是911的十周年,基地组织又想攻击美国了,美国有建设了一堵墙来保护自己,而基地组织有一个超级武器,每秒钟可以攻击连续的墙。而美国还有能量护盾来保护墙,每个能量护盾覆盖在一个单位长度的墙上,能抵挡攻击,但是每个能量护盾防御了一次攻击都需要T秒时间冷却下来,冷却期间,它不能抵挡攻击,比如该单位初盾牌抵挡了第k次攻击,那么它不能抵挡第k+1~(k+t-1)次攻击,过后,他们自动继续防御。

在战争中,知己知彼是非常重要的,因此指挥官想知道墙的某一部分被成功攻击了多少次,成功攻击就意味着盾牌没有防御到

 

输入

第一行T(<=20),表示测试数据组数

每组测试数据第一行:N,Q,T,分别表示墙的长度,Q次攻击或询问,T秒的冷却时间

接下来Q行,格式有两种:

Attack si ti

 攻击 si ti 的墙. 1 si ti N

Query p

询问p位置的墙:1 ≤ p ≤ N

1 ≤ N, Q ≤ 20000
1 ≤ t ≤ 50

 

输出

每组数据第一行:

Case i:

接下来是所有询问的答案,每个询问答案一行

 

样例输入

2

3 7 2

Attack 1 2

Query 2

Attack 2 3

Query 2

Attack 1 3

Query 1

Query 3

9 7 3

Attack 5 5

Attack 4 6

Attack 3 7

Attack 2 8

Attack 1 9

Query 5

Query 3

 

样例输出

Case 1:

0

1

0

1

Case 2:

3

2

 

暂时没A,先放这里吧~

 

Poj 2985 The k-th Largest Group

题意

N只猫,M个操作。操作种类如下:

0 a b:把a b猫所在的组合并

1 k K大的组的大小是多少。

 

求第k大的数

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX=200020;
int father[MAX],e[MAX];
int cnt[MAX];//存储有i只猫的集合数 
int n,m,op,a,b,num;
int lowbit(int x){
	return -x&x;
}
void add(int x,int t){
	for(;x<=MAX;x+=lowbit(x))
		e[x]+=t;
}
int Getfather(int p){
	if(father[p]!=p) 
		father[p]=Getfather(father[p]);
	return father[p];
}
int Get_kth(int k){
	int ans,tot;//二分找第k小 
	ans=tot=0;
	for(int i=20;i>=0;i--){
		ans+=1<<i;//尝试 
		if(ans>=MAX||tot+e[ans]>=k)//看这次尝试是否超过范围 
			ans-=1<<i;//复原 
		else tot+=e[ans];//记录 
	}
	return ans+1;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		father[i]=i;//并查集初始化 
		cnt[i]=1;//表示组内有i只猫的组数,开始只有一组 
	}num=n;//num个集合 
	add(1,n);
	for(int i=1;i<=m;i++){
		scanf("%d",&op);
		if(op){
			scanf("%d",&a);//第k大就是第num - k + 1小的
			printf("%d\n",Get_kth(num-a+1));
		}else{
			scanf("%d%d",&a,&b);
			a=Getfather(a); b=Getfather(b);//取父亲 
			if(a==b) continue;//本来属于一个集合 
			add(cnt[a],-1); add(cnt[b],-1);
			add(cnt[a]+cnt[b],1);
			cnt[b]=cnt[a]+cnt[b];
			father[a]=b;
			num--;
		}
	}
	return 0;
}


Poj 1195 Mobile phones

题意:

给你一个矩阵(初始化为0)和一些操作:

1 x y a表示在arr[x][y]加上a

2 l b rt 表示求左上角为(l,b),右下角为(r,t)的矩阵的和。

 

高维树状数组即可

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX=1200;
int e[MAX][MAX],n;
int lowbit(int i){
	return -i&i;
}
int sum(int x,int y){
	int ans=0;
	while(x>0){
		int ty=y;
		while(ty>0){
			ans+=e[x][ty];
			ty-=lowbit(ty);
		}
		x-=lowbit(x);
	}
	return ans;
}

void add(int x,int y,int t){
	while(x<=n){
		int ty=y;
		while(ty<=n){
			e[x][ty]+=t;
			ty+=lowbit(ty);
		}
		x+=lowbit(x);
	}
}
int main(){
//	freopen("in.txt","r",stdin);
	int c,x,y,a,x1,x2,y1,y2;
	scanf("%d%d",&c,&n);
	while(~scanf("%d",&c)&&c!=3){
		if(c==1){
			scanf("%d%d%d",&x,&y,&a);
			add(x+1,y+1,a);
		}else{
			scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			int ans=sum(x2+1,y2+1)-sum(x1,y2+1)-sum(x2+1,y1)+sum(x1,y1);
			printf("%d\n",ans);
		}
	}
	
	return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 JavaScript 中,可以使用 map() 函数将一维数组转换为树形结构。首先,需要对数组元素进行处理,使其具有 id、parentId 等属性,然后遍历数组,将元素划分到合适的父节点下。代码示例如下: ``` function convertToTree(data) { let tree = []; let map = {}; data.forEach(function (item) { map[item.id] = item; }); data.forEach(function (item) { let parent = map[item.parentId]; if (parent) { (parent.children || (parent.children = [])).push(item); } else { tree.push(item); } }); return tree; } ``` ### 回答2: 将一维数组转为数据结构,可以考虑使用树的结构。树是一种非线性的数据结构,它由节点和边组成,每个节点可能有多个子节点,但只能有一个父节点。 我们可以将一维数组中的元素逐个添加到树的节点中。首先,创建一个根节点,将数组的第一个元素作为根节点的值。然后,遍历数组的剩余元素,对每个元素创建一个新的节点,并将其作为根节点的子节点。如果一个节点已经有子节点,就将新节点作为其子节点的子节点。重复此过程,直到遍历完整个数组。 通过这种方式,我们可以将一维数组转化为一个树结构。在树中,每个节点都对应数组中的一个元素,而节点的子节点对应数组中位于该元素后面的元素。可以通过遍历树的节点,获取到数组中的每个元素。 使用树结构可以更方便地组织、访问和操作一维数组中的元素。树提供了一种分层的结构,可以方便地进行查找、插入和删除操作。此外,树还可以用来表示具有层次关系的数据,比如文件系统或者组织架构。 总结来说,将一维数组转为树结构可以更好地组织和操作数组中的元素,提供更方便的数据访问方式。这种转化可以通过将数组的元素逐个添加到树的节点中来完成。 ### 回答3: 将一维数组转换为数据结构可以考虑使用树状结构。树状结构是一种非线性的数据结构,由节点和边组成,可以表示层级关系。在JS中,可以使用对象来表示树状结构。 首先,可以创建一个空对象作为根节点。然后,遍历一维数组,将数组中的每个元素作为节点添加到树中。可以使用循环来遍历数组,对于每个元素,可以判断该节点是否存在,如果不存在则创建一个新的节点对象,并将其添加到上层节点的children属性中。如果节点已经存在,则将当前节点设置为新的上层节点。 以下是一个简单的示例代码: ```javascript function arrayToTree(arr) { const root = {}; for (let i = 0; i < arr.length; i++) { let currentNode = root; const path = arr[i].split('.'); for (let j = 0; j < path.length; j++) { if (!currentNode[path[j]]) { currentNode[path[j]] = {}; } currentNode = currentNode[path[j]]; } } return root; } const arr = ['a', 'b.c', 'b.d.e', 'f']; const tree = arrayToTree(arr); console.log(tree); ``` 在上面的代码中,我们定义了一个arrayToTree函数,该函数接收一个一维数组作为参数,并返回一个树状结构的对象。我们使用root对象作为根节点,然后遍历数组中的每个元素,将其拆分为字符串数组,每个元素表示树状结构的一层。然后使用嵌套的循环来创建节点并添加到相应的位置。 最后,将给定的一维数组转换为树状结构。在示例中,数组['a', 'b.c', 'b.d.e', 'f']被转换为以下树状结构: ``` { "a": {}, "b": { "c": {}, "d": { "e": {} } }, "f": {} } ``` 这样,我们就成功将一维数组转换为了树状结构。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值