树状数组相关题目

题目一

方法一

归并分治

代码:

# include <stdio.h>int arr[100];
int help[100];int n;//归并分治
// 1.统计i、j来自 l~r 范围的情况下,逆序对数量
// 2.统计完成后,让arr[l...r]变成有序的 
int f(int l, int r)
{if (l == r)return 0;int m = (l + r) / 2;return f(l, m) + f(m+1, r) + merge(l, m, r);
}int merge(int l, int m, int r)
{//i 来自 l...m//r 来自 m+1....r//统计有多少逆序对 int ans = 0;int j = r;for (int i=m; i>=l; --i){while (j >= m+1 && arr[i] <= arr[j])j = j - 1;ans = ans + j - m;}//左右部分合并,整体变有序,归并排序的过程 int k = l;int a = l;int b = m + 1;while (a <= m && b <= r){if (arr[a] <= arr[b]){help[k] = arr[a];a = a + 1;}else{help[k] = arr[b];b = b + 1;}k = k + 1;}while (a <= m){help[k] = arr[a];k = k + 1;a = a + 1;}while (b <= m){help[k] = arr[b];k = k + 1;b = b + 1;}for (i = l; i <= r; ++i)arr[i] = help[i];return ans; 
}int main()
{scanf("%d", &n);for (int i=0; i<n; ++i)scanf("%d", &arr[i]);int ans = f(0, n-1);
}

方法二

树状数组

我们假设一个数组arr

建立一个词频数组

用于记录数字出现的次数

因为arr数组中最大的数字为100

所以只要建立一个长度为100的词频数组即可

我们对原数组从右往左进行考虑

先考虑arr[8](也就是2)

对于逆序对,i  <  j,并且arr[i] > arr[j]

因此我们只要统计小于2的数,在词频数组中的和即可

也就是词频数组中下标2之前的前缀和

在统计完sum后,再将2添加进词频数组中

这时,我们就可以把词频数组转为树状数组

以上的操作就变为了

单点修改和区间查询

注意:

此时要考虑树状数组长度的问题!!!

因为词频数组的长度是由原数组最大值决定的,可能会出现过大的情况

此时就要进行离散化处理

我们可以假设另外一个数组help

将原数组按照从小到大的顺序填入help数组中

并进行去重(可以使用双指针)

然后通过help数组对arr数组进行修改

利用二分

找到原数组中的数字在help数组中的下标位置

并将该数字修改为help数组中的下标的值

  

再对此时的arr数组进行词频统计建立树状数组

代码:

# include <stdio.h>
# include <stdlib.h>
int arr[100];
int sort[100];
int tree[100];int n, m;int cmp(const void* a, const void* b)
{int p1 = *(int*)a;int p2 = *(int*)b;return p1 - p2;
}int lowbit(int x)
{return x & (-x);
}void add(int i, int v)
{while (i <= m){tree[i] = tree[i] + m;i = i + lowbit(x);}
}int sum(int i)
{int ans = 0;while (i > 0){ans = ans + tree[i];i = i - lowbit(i;)}return ans;
}// 给定原始值v
// 返回sort数组中1~m的下标 
int rank(int v) 
{int l = 1;int r = m;int mid;int ans = 0;while (l<=r){mid = (l + r) / 2;if (sort[mid] >= v){ans = mid;r = mid - 1;}else{l = mid + 1;}}return ans;
}int main()
{scanf("%d", &n);for (int i = 1; i <= n; ++i){scanf("%d", arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(int), n, cmp);m = 1;for (int i=2; i <= n; ++i){if (sort[i] != sort[m]){m = m + 1;sort[m] = sort[i];}}int ans = 0;for (int i=1; i<=n; ++i)arr[i] = rank(arr[i]);for (int i=n; i>=1; --i){//增加当前数字的词频 add(arr[i], 1);// 右边有多少数字是 <= 当前数值 - 1 ans = ans + sum(arr[i]-1);}printf("%d", ans);
}

两个方法的区别:

归并分治实现:无需离散化代码、使用空间少、常数时间优良,不能实时查询,只能是离线的批量过程

树状数组实现:需要离散化代码、使用空间多、常数时间稍慢,可以实时查询

题目二(典型)

我们建立两个数组up1, up2

up1[ i ]:以下标 i 做结尾的升序的只有一个元素的数量

up2[ i ]:以下标 i 做结尾的升序的含有两个元素的数量

假设数组arr为:

从左往右进行处理

我们加入第一个数字为arr[1] = 1

先判断以 1 做结尾的升序的含有三个元素的数量

我们发现该值就是在up2数组中小于1的前缀和

然后再更新以 1 做结尾的升序的含有两个个元素的数量(也就是up2[1])

我们发现该值就是在up1数组中小于1的前缀和

找到该值后,填入up2[1]

更新以 1 做结尾的升序的含有一个元素的数量(也就是up1[1])

依次对 arr 数组从左往右进行遍历

因此,我们先对数组中的元素进行离散化处理

然后从左往后按照上述步骤进行

代码:

# include <stdio.h>
# include <string.h>
//记录原数组 
int arr[100];//建立去重离散化数组 
int sort[100];//维护信息:up1
//tree1不是up1数组,是up1数组的树状数组 
int tree1[100]; //维护信息:up2
//tree2不是up2数组,是up2数组的树状数组 
int tree2[100];int n, m;int cmp(const void* a, const void* b)
{int* p1 = (int*)a;int* p2 = (int*)b;return *p1 - *p2;
}int lowbit(int x)
{return x & (-x);
}void add(int* tree, int i, int v)
{while (i <= m){tree[i] = tree[i] + v;i = i + lowbit(i);}
}int sum(int* tree, int i)
{int ans = 0;while (i > 0){ans = ans + tree[i];i = i - lowbit(i);}return ans;
}int rank(int x)
{int l = 1;int r = m;int mid;int ans = 0;while (l <= r){mid = (l + r) / 2;if (sort[mid] >= v){ans = mid;r = mid - 1;}else	l = mid + 1;}return ans;
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(int), n, cmp);m = 1;for (int i=2; i<=n; ++i){if (sort[m] != sort[i]){m = m + 1;sort[m] = sort[i];}}for (int i=1; i<=n; ++i)arr[i] = rank(arr[i]);int ans = 0;for (int i=1; i<=n; ++i){ans = ans + sum(tree2, arr[i] - 1);add(tree1, arr[i], 1);add(tree2, arr[i], sum(tree2, arr[i] - 1));}	printf("%d", ans);
}

题目三

我们建立一个结构

用于存储以下标 i 为结尾的子序列的最大长度和个数

# include <stdio.h>struct node
{int len; //长度 int num; //个数 
};//不是该结构数组,是该结构体的树状数组
//用于维护一个范围的最大长度和个数
struct node tree[100];int arr[100];
int sort[100];int n, m;//查询结尾数值<=i的所有递增子序列中
//长度最大的递增子序列长度是多少,赋值给maxlen和maxnum
int maxlen, maxnum;int lowbit(int x)
{return x & (-x);
}//以 i 这个值结尾的最长递增子序列达到了len长度
//并且这样的最长递增子序列的数量达到了cnt个
void add(int i, int l, int cnt)
{while (i <= m){if (tree[i].len == l)tree[i].num = tree[i].num + cnt;else if (tree[i].len < l){tree[i].len = l;tree[i].num = cnt;}i = i + lowbit(i);}
}//结尾数值 <= i 的情况下,最长递增子序列的长度和个数为多少
void query(int x)
{maxlen = 0;maxnum = 0;while (x > 0){if (tree[x].len == maxlen)maxnum = tree[x].num + maxnum;else if (tree[x].len > maxlen){maxlen = tree[x].len;maxnum = tree[x].num;}x = x - lowbit(x);}
}int rank(int x)
{int l = 1;int r = m;int mid;int ans;while (l <= r){mid = (l+r) / 2;if (sort[mid] >= x){r = mid - 1;ans = mid;}elsel = mid + 1;}return ans;
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);sort[i] = arr[i];}qsort(sort, sizeof(sort), n, cmp);int m = 1;for (int i=2; i<=n; ++i){if (sort[m] != sort[i]){sort[m] = sort[i];m = m + 1;}}memset(tree, 0, sizeof(tree));int y;for (int i=1; i<=m; ++i){y = rank(arr[i]);query(y-1);add(y, maxlen+1, max(1, maxnum));}int ans = query(m);printf("%d", ans);
}

题目四

本题转化是非常巧妙的

我们首先需要对查询进行排序

按照 r 的大小进行排序

如图:

一个虚拟数组cnt[](之所以说它是虚拟数组,因为可以用树状数组实现想要的操作,不需要真的定义这么一个数组)。

map[i] 记录 i 位置上的数上一次出现的位置

当遍历到原数组的下标 i 时,cnt[i]+1,如果 i 位的这个数之前出现过,即存在map[i],则将cnt[map[i]]-1(在遍历到i之前已经遍历过last[i]位置了,因此cnt[map[i]]=1,所以这样操作下来,cnt[map[i]]=0),不会对当前要求的结果造成影响

比如序列:2,3,4,3,4,2

当右边界为5时(下标从1开始),对应的cnt[]值分别为:1,0,0,1,1

所以区间【1,5】内不同的数字个数=3,区间【2,5】内不同的数字个数=2,区间【4,5】内不同的数字个数=2(树状数组累加和求解)

因为右边界一定,所以我们必须要确保选择的1是尽可能靠近右边界,确保答案正确

# include <stdio.h>
# include <string.h>
struct node
{int l;int r;int k;
};int arr[100];struct node query[100];int ans[100];int map[100];int tree[100];int n, m;int compare(const void*a, const void* b)
{struct node* p1 = (struct node*)a;struct node* p2 = (struct node*)b;return *p1.r - *p2.r;
}int lowbit(int x)
{return x & (-x);
}void add(int i, int v)
{while (i <= n){tree[i] = tree[i] + v;i = i + lowbit(i);}
}int q(int x)
{int ans = 0;while (x > 0){ans = tree[x] + ans;x = x - lowbit(x);}return ans;
}int range(int l, int r)
{return sum(r) - sum(l-1);
}void cmp(void)
{qsort(query, sizeof(struct node), m, compare);int q = 1; //遍历询问 int l;int r;int k; for (int q=1; q <= m; ++i){r = query[q].r;for (; s<=r; ++s){int color = arr[s];if (map[color] != 0)add(map[color], -1)add[s, 1];map[color] = s;}l = query[q].l;k = query[q].k;ans[i] = range(l, r);}
}int main()
{scanf("%d", &n);for (int i=1; i<=n; ++i){scanf("%d", &arr[i]);}scanf("%d", &m);for (int i=1; i<=m; ++i){scanf("%d %d", &query[i].l, &query[i].r);query[i].k = i;}cmp();for (int i=1; i<=m; ++i)printf("%d", ans[i]);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/595870.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis 之 useGeneratedKeys

数据库中主键基本都设置为自增&#xff0c;当我们要插入一条数据想要获取这条数据的 Id 时&#xff0c;就可使用 Mybatis 中的 useGeneratedKeys 属性。 背景 这里以 苍穹外卖 中的 新增菜品 功能为例&#xff0c;有 菜品表(dish table)和 口味表(dish_flavor table)&#xf…

C#,简单,精巧,实用的文件夹时间整理工具FolderTime

点击下载本文软件&#xff08;5积分&#xff09;&#xff1a; https://download.csdn.net/download/beijinghorn/89071073https://download.csdn.net/download/beijinghorn/89071073 百度网盘&#xff08;不需积分&#xff09;&#xff1a; https://pan.baidu.com/s/1FwCsSz…

鸿蒙OS开发实例:【组件化模式】

组件化一直是移动端比较流行的开发方式&#xff0c;有着编译运行快&#xff0c;业务逻辑分明&#xff0c;任务划分清晰等优点&#xff0c;针对Android端的组件化&#xff1b;与Android端的组件化相比&#xff0c;HarmonyOS的组件化可以说实现起来就颇费一番周折&#xff0c;因为…

5G随身wifi真实测评!飞猫5g随身wifi怎么样?飞猫5GVS格行5G随身wifi哪款网速快?5G随身wifi推荐品牌第一名!

飞猫5G随身wifi&#xff1a; 产品外观&#xff1a;黑色大气外观&#xff0c;净重175g&#xff0c;屏幕有信号和指示灯。 产品性能&#xff1a;采用展锐芯片。6根LDS天线&#xff0c;网速100-200mbps&#xff0c;网络延迟10-20ms&#xff0c;2.4G/5G双频可选&#xff0c;超稳定…

第十三届蓝桥杯C++A组 - B/D/E

文章目录 前言一、灭鼠先锋1.题目描述2.算法 二、选数异或1.题目描述2.算法 三、爬树的甲壳虫1.问题描述2.算法 前言 题目考点灭鼠先锋bfs博弈论MEX运算SG函数选数异或二分线段树爬树的甲壳虫快速幂逆元扩展欧几里得裴蜀定理dp 一、灭鼠先锋 1.题目描述 2.算法 我们先要确定…

[蓝桥杯 2017 国 C] 合根植物

[蓝桥杯 2017 国 C] 合根植物 题目描述 w 星球的一个种植园&#xff0c;被分成 m n m \times n mn 个小格子&#xff08;东西方向 m m m 行&#xff0c;南北方向 n n n 列&#xff09;。每个格子里种了一株合根植物。 这种植物有个特点&#xff0c;它的根可能会沿着南北…

C语言动态内存讲解+通讯录2.0

文章目录 前文malloc和freecallocrealloc枚举常量的简单说明及使用 通讯录2.0动态开辟通讯录,满了就扩容保存数据和载入数据 通讯录2.0演示推荐好用的软件 前文 本文主要介绍动态开辟的几个函数,以及改进之前的通讯录。 我们局部变量等是在栈区上开辟空间的,而我们动态开辟的空…

16 - 程序计数器和内存

---- 整理自B站UP主 踌躇月光 的视频 1. 程序计数器 程序计数器需要支持后续程序的运行&#xff0c;需要支持跳转&#xff0c;所以需要一个预置数的功能。我们在 ALU 前面加上个寄存器。 2. 内存控制器 3. 通过程序计数器读取内存 辅助工具 4. 实验工程 【16 - 程序计数器和…

springBoot--阿里云短信验证

阿里云短信验证 前言阿里云短信服务免费领取100条短信服务1、开通短信服务2、申请签名3、申请模板4、通过子用户获取账号的AccessKey ID 和AccessKey Secret5、使用教程 前言 在我们平时登录中短信验证吗验证在当今是必不可少的&#xff0c;下面是基于阿里云开发的短信验证操作…

【C语言基础】:文件操作详解(前篇:准备知识)

文章目录 一、什么是文件以及文件的分类1.1 程序文件1.2 数据文件1.3 文件名 二、文本文件和二进制文件2.1 数据在文件中的存储 三、文件的打开和关闭3.1 流和标准流3.1.1 流3.1.2 标准流 3.3 文件指针3.5 文件的打开和关闭 一、什么是文件以及文件的分类 文件是指存储在计算机…

文件服务器之二:SAMBA服务器

文章目录 什么是SAMBASAMBA的发展历史与名称的由来SAMBA常见的应用 SAMBA服务器基础配置配置共享资源Windows挂载共享Linux挂载共享 什么是SAMBA 下图来自百度百科 SAMBA的发展历史与名称的由来 Samba是一款开源的文件共享软件&#xff0c;它基于SMB&#xff08;Server Messa…

《QT实用小工具·十六》IP地址输入框控件

1、概述 源码放在文章末尾 该项目为IP地址输入框控件&#xff0c;主要包含如下功能&#xff1a; 可设置IP地址&#xff0c;自动填入框。 可清空IP地址。 支持按下小圆点自动切换。 支持退格键自动切换。 支持IP地址过滤。 可设置背景色、边框颜色、边框圆角角度。 下面…