首页 > 其他分享 >树状数组

树状数组

时间:2024-01-05 18:45:11浏览次数:26  
标签:结点 树状 int lowbit ask 数组 logn

给出一个长度为nn的数组,完成以下两种操作:
1. 将第ii个数加上kk
2. 输出区间[i,j][i,j]内每个数的和

朴素算法
单点修改:O(1)O(1)
区间查询:O(n)O(n)
使用树状数组
单点修改:O(logn)O(logn)
区间查询:O(logn)O(logn)
前置知识
lowbit()lowbit()运算:非负整数xx在二进制表示下最低位1及其后面的0构成的数值。

举例说明:
lowbit(12)=lowbit([1100]2)=[100]2=4

 

树状数组思想
树状数组的本质思想是使用树结构维护”前缀和”,从而把时间复杂度降为O(logn)O(logn)。

对于一个序列,对其建立如下树形结构:

每个结点t[x]保存以x为根的子树中叶结点值的和
每个结点覆盖的长度为lowbit(x)
t[x]结点的父结点为t[x + lowbit(x)]
树的深度为log2n+1

 

add(x, k)表示将序列中第x个数加上k。

以add(3, 5)为例:
在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。时间复杂度为O(logn)O(logn)。
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i))
t[i] += k;
}
ask(x)表示将查询序列前x个数的和

以ask(7)为例:
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),例如 7 - lowbit(7) = 6。
int ask(int x)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i))
sum += t[i];
return sum;
}

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 2000010;
 8 
 9 typedef long long LL;
10 
11 int n;
12 //t[i]表示树状数组i结点覆盖的范围和
13 int a[N], t[N];
14 //Lower[i]表示左边比第i个位置小的数的个数
15 //Greater[i]表示左边比第i个位置大的数的个数
16 int Lower[N], Greater[N];
17 
18 //返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
19 int lowbit(int x)
20 {
21     return x & -x;
22 }
23 
24 //将序列中第x个数加上k。
25 void add(int x, int k)
26 {
27     for(int i = x; i <= n; i += lowbit(i)) t[i] += k;
28 }
29 //查询序列前x个数的和
30 int ask(int x)
31 {
32     int sum = 0;
33     for(int i = x; i; i -= lowbit(i)) sum += t[i];
34     return sum;
35 }
36 
37 int main()
38 {
39 
40     scanf("%d", &n);
41     for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
42 
43     //从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
44     for(int i = 1; i <= n; i++)
45     {
46         int y = a[i]; //第i个数
47 
48         //在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
49         Lower[i] = ask(y - 1); 
50 
51         //在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
52         Greater[i] = ask(n) - ask(y);
53 
54         //将y加入树状数组,即数字y出现1次
55         add(y, 1);
56     }
57 
58     //清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
59     memset(t, 0, sizeof t);
60 
61     LL resA = 0, resV = 0;
62     //从右往左统计
63     for(int i = n; i >= 1; i--)
64     {
65         int y = a[i];
66         resA += (LL)Lower[i] * ask(y - 1);
67         resV += (LL)Greater[i] * (ask(n) - ask(y));
68 
69         //将y加入树状数组,即数字y出现1次
70         add(y, 1);
71     }
72 
73     printf("%lld %lld\n", resV, resA);
74 
75     return 0;
76 }
Code

 

标签:结点,树状,int,lowbit,ask,数组,logn
From: https://www.cnblogs.com/rw666/p/17947839

相关文章

  • JavaScript——数组的归并方法
    JavaScript的reduce和reduceRight的作用是通过遍历数组得到一个结果,原理如下:functionmyReduce(execute,initValue){constlength=this.lengthletresultfor(leti=0;i<length;i++){if(i===0){consthasInitValue=initV......
  • 数组
    数组的概述数组的特点:数组是有序排列的。1、数组属于引用数据类型的变量。数组的元素既可以是基本数据类型也可以是引用数据类型。2、创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。3、数组的长度一旦确定,就不能修改。数组的分类按......
  • MySQL 8.0的SQL查询JSON返回的数据类型为字符串而非数组
    在MySQL8.0中,SQL查询JSON返回的数据类型确实是字符串,而不是数组。这是因为MySQL将JSON数据存储为字符串,并提供了一些函数和操作符来处理JSON数据。但是,你可以使用内置的JSON函数来处理返回的JSON字符串。例如,你可以使用JSON_EXTRACT函数来提取JSON字符串......
  • 指针数组与数组指针的区别及相关知识
    区别:指针数组:定义int*p[n]可称为指针的数组,是数组,数组里的元素都是指针。也就是说数组存储的是指针,数组占多少字节由数组本身决定。指针数组+1不同类型的变化如下//eg:用指针parr指向一个一维数组intmain(){ int*parr[5]={0,1,2,3,4}; printf("%x\n",parr);//数组名代......
  • 2024-01-03:用go语言,给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time, 分别表示
    2024-01-03:用go语言,给你两个长度为n下标从0开始的整数数组cost和time,分别表示给n堵不同的墙刷油漆需要的开销和时间。你有两名油漆匠,一位需要付费的油漆匠,刷第i堵墙需要花费time[i]单位的时间,开销为cost[i]单位的钱。一位免费的油漆匠,刷任意一堵墙的时间为1......
  • 稀疏数组
    问题介绍需求:编写五子棋游戏中,有存盘退出和续上盘的功能。 分析问题:因为二维数组的很多值是默认值0,因此记录了很多没有意义的数据。解决:稀疏数组概念当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组。稀疏数组的处理方式是:记录数组一共有......
  • 【C++】STL 容器 - stack 堆栈容器 ① ( stack 堆栈容器特点 | stack 堆栈容器与 dequ
    文章目录一、stack堆栈容器简介1、stack堆栈容器引入2、stack堆栈容器特点3、stack堆栈容器与deque双端数组容器对比二、代码示例-stack堆栈容器简单示例1、代码示例2、执行结果一、stack堆栈容器简介1、stack堆栈容器引入C++语言中的STL标准模板库中的stac......
  • 数组指针的用法
    #define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>//参数是数组形式voidprint1(intarr[3][5],intx,inty)//用数组形式接收,再接收传来的参数{ inti=0; intj=0; for(i=0;i<x;i++) { for(j=0;j<y;j++) { printf("%d",arr[i][j]);......
  • 数组指针
    数组指针是指针 用来存放数组的地址#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>intmain(){ //eg: //int*P=NULL;//p是整形指针-指向整型的指针-可以存放整型的地址 //char*pc=NULL;//pc是字符指针-指向字符的指针-可以存放字符的地址 //数组指针-指......
  • 指针数组
    指针数组是数组是用来存放指针的eg:指针数组用法#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>intmain(){ //int*parr[4];//存放整型指针的数组--指针数组 //char*pch[5];//存放字符指针的数组--指针数组 intarr1[5]={1,2,3,4,5}; intarr2[5]={2,3,4,5,6......