树状数组模板题:楼兰图腾

2023-10-13 15:59

本文主要是介绍树状数组模板题:楼兰图腾,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目链接:https://www.acwing.com/problem/content/description/243/

题目:

在完成了分配任务之后,西部 314 来到了楼兰古城的西部。

相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V),一个部落崇拜铁锹(),他们分别用 V 和  的形状来代表各自部落的图腾。

西部 314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 n 个点,经测量发现这 n 个点的水平位置和竖直位置是两两不同的。

西部 314 认为这幅壁画所包含的信息与这 n 个点的相对位置有关,因此不妨设坐标分别为 (1,y1),(2,y2),…,(n,yn),其中 y1∼yn 是 1 到 n 的一个排列。

西部 314 打算研究这幅壁画中包含着多少个图腾。

如果三个点 (i,yi),(j,yj),(k,yk) 满足 1≤i<j<k≤n 且 yi>yj,yj<yk,则称这三个点构成 V 图腾;

如果三个点 (i,yi),(j,yj),(k,yk)满足 1≤i<j<k≤n 且 yi<yj,yj>yk,则称这三个点构成  图腾;

西部 314 想知道,这 n 个点中两个部落图腾的数目。

因此,你需要编写一个程序来求出 V 的个数和  的个数。

输入格式

第一行一个数 n。

第二行是 n个数,分别代表 y1,y2,…,yn。

输出格式

两个数,中间用空格隔开,依次为 V 的个数和  的个数。

数据范围

对于所有数据,n≤200000,且输出答案不会超过 int64。
y1∼yn 是 1 到 n 的一个排列。

输入样例:

5
1 5 3 2 4

输出样例:

3 4

思路:

暴力的方式:

求 ∧的个数

求i左边比a[i]小的值,以及i右边,比a[i]小的值。 然后乘起来,

同样 i + 1的也是如此, i + 2的也是如此。

然后每个对应i上能够组成的总和加起来就是答案。  

V 的个数也是一样。

暴力求的话,时间复杂度为O(n ^ 2).

举例来说明树状数组的用途:将原先O(n)求某段区间的和,变为了O(logn)求这个区间的和。但本质没变,都是求a[1] + a[2] + ....+ a[n]的和。

如存入3.则c[3] += 1,c[4] += 1, c[8] += 1.

而当我们求解1~4的和时, 输出的就是c[4]. c[4]的含义就是以4为终点,长度为lowbit(4)也就是4 的和。(即4,3,2,1出现的总次数)。

而当我们求解2~4的和时,就使用c[4] - c[1]即可。所以依然为1, 因为只有3出现了并只出现了1次。

画图举例:

所以求某个i左边区间中比a[i]小的数个数总和,其实就是求左边这个区间在1~a[i] - 1中数值出现的次数和。 所以将a[1~i-1]的值出现的次数存入到树状数组中, 

如i == 3 , a[1] = 5, a[2] = 6, a[3] = 10,

由 a[1] == 5, 所以对应的5出现次数 + 1,从而树状数组改变,c[5] += 1, c[6] += 1,c[8] += 1.

a[2] == 6,所以对应的6出现次数 + 1, 从而树状数组改变, c[6] += 1, c[8] += 1.

到了a[3]的时候,所以求 1~10 - 1这些数值出现的次数。 于是sum(9) - sum(1 - 1)

c[9] + c[8] - c[0] = 2.

所以可以看出实际上还是求1~9出现次数的和,但是从原先的暴力(1出现次数 + 2出现次数+...+9出现的次数)   优化为了 c[9] + c[8] - c[0]. 

这就是树状树组的优化,由原先的O(n)求区间和, 变为了O(logn)求区间和。

而每修改一次 某个a[]出现的次数 就修改一次c[],主要是因为后面需要通过c[]求区间和,所以修改。

所以是否可以用树状数组对我们每次的暴力求左边区间小于a[i]的次数和进行优化呢?实际上是可以的。

以求 ∧的个数为例,

求i左边的1~i - 1的区间中,小于 a[i]这个值的出现次数和。 

每一次遍历i,都将对应的a[i]值加入到对应树状数组中,也就是a[i]这个值出现次数 + 1,从而使得c[]改变, 当到达了a[i]时,就通过树状数组在O(logn)的时间内求出 1~a[i - 1]出现的总次数。使用sun(a[i - 1]) - sum(1 - 1).

求i左边的1 ~ i - 1的区间中,大于a[i]这个值的出现次数的和。也同上,每次将a[i]的值存入到树状数组中,也就是a[i]值出现次数 + 1, 从而使得c[]改变,当到达a[i]时, 就通过树状数组求

a[i] + 1 ~ n出现的总次数。 sum(n) - sum(a[i] + 1).

从而用树状数组求解所有 i左边 比a[i]小的次数 t1,比a[i]大的次数t2, 右边比a[i]小的次数t3,比a[i]大的次数t4.

每一个i的t1 * t3 的总和。

每一个i的 t2 * t4的总和。

总结:

当求某个区间的总和 并且 需要 单点修改某个点的值时,看是否可以使用树状数组进行优化。

代码实现:

/*
c[i]的含义就是 以i为终点,长度为lowbit(i)的前缀和
*/
# include <iostream>
# include <cstring>
using namespace std;const int N = 200010;int a[N];
int c[N];int lower[N]; //表示左边比第i个位置小的数的个数
int up[N];  //表示左边比第i个位置大的数的个数int n;int lowbit(int x)
{return x & -x;
}void add(int x, int k)
{for(int i = x; i <= n; i += lowbit(i)) c[i] += k;
}int sum(int x)
{int res = 0;for(int i = x ; i ; i -= lowbit(i)){res += c[i];}return res;
}int main()
{scanf("%d",&n);for(int i = 1; i <= n ; i++){scanf("%d",&a[i]);}for(int i = 1; i <= n ; i++){int y = a[i];lower[i] = sum(y - 1);  // 找i的左边, 所有 1 ~ y - 1的总个数up[i] = sum(n) - sum(y);  // 找到i的左边 所有 y + 1 ~ n的总个数add(y,1);}memset(c,0,sizeof c); // 将c清空long long v = 0;long long d = 0;for(int i = n ; i >= 1 ; i--){int y = a[i];d += (long long)sum(y - 1) * lower[i];v += (long long)( sum(n) - sum(y) ) * up[i];add(y,1);}printf("%lld %lld\n",v,d);return 0;
}

这篇关于树状数组模板题:楼兰图腾的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/204339

相关文章

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

Java中数组转换为列表的两种实现方式(超简单)

《Java中数组转换为列表的两种实现方式(超简单)》本文介绍了在Java中将数组转换为列表的两种常见方法使用Arrays.asList和Java8的StreamAPI,Arrays.asList方法简... 目录1. 使用Java Collections框架(Arrays.asList)1.1 示例代码1.

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

基于Java实现模板填充Word

《基于Java实现模板填充Word》这篇文章主要为大家详细介绍了如何用Java实现按产品经理提供的Word模板填充数据,并以word或pdf形式导出,有需要的小伙伴可以参考一下... Java实现按模板填充wor编程d本文讲解的需求是:我们需要把数据库中的某些数据按照 产品经理提供的 word模板,把数据