树状数组相关题目

news2024/11/17 16:21:00

题目一

方法一

归并分治

代码:

# 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;
		}
		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(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.coloradmin.cn/o/1573759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html写一个登录注册页面

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>注册登录界面Ⅰ</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.mi…

CSS属性计算逻辑

CSS 属性计算逻辑 首先&#xff0c;假设在 HTML 中有这么一段代码&#xff0c;在 body 中有一个 h1 标题&#xff1a; <body><h1>这是一个h1标题</h1> </body>目前我们没有设置该 h1 的任何样式&#xff0c;但是却能看到该 h1 有一定的默认样式&…

PHP实现网站微信扫码关注公众号后自动注册登陆实现方法及代码【关注收藏】

在网站注册登陆这环节&#xff0c;增加微信扫码注册登陆&#xff0c;普通的方法需要开通微信开发者平台&#xff0c;生成二维码扫码后才能获取用户的uinonid或openid&#xff0c;实现注册登陆&#xff0c;但这样比较麻烦还要企业认证交费开发者平台&#xff0c;而且没有和公众号…

Linux:五种IO模型的基本认识

文章目录 IO的本质五种IO模型异步和同步 阻塞IO非阻塞IO信号驱动IO IO的本质 在之前的内容中已经结束了对于网络和操作系统的学习&#xff0c;那么回过来再继续看IO&#xff0c;什么是IO呢&#xff1f; 对于网络的学习当中&#xff0c;实际上也是一种IO&#xff0c;数据从计算…

基于单片机钢琴电子节拍器系统设计

**单片机设计介绍&#xff0c;基于单片机钢琴电子节拍器系统设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机钢琴电子节拍器系统设计是一个综合性的项目&#xff0c;它结合了单片机编程、音频处理、用户界面设计等多个领域的…

Ai软件下载安装 Adobe Illustrator一键安装 5分钟搞定

ai软件又叫adobe illustrator软件,它是设计者们比较常用的平面设计软件,不仅可以用来学习日常的学习设计,还能够用作于商业设计用途,作为一款全球知名的矢量图形处理工具,ai软件广泛应用于印刷出版、海报书籍排版、专业插画、多媒体图像处理和互联网页面的制作等.从事设计行业的…

Ceph学习 - 2.分布式文件系统DFS

文章目录 1.分布式文件系统DFS1.1 DFS简介1.1.1 存储基础1.1.2 分布式文件系统1.1.3 DSS简介1.1.4 常见的文件系统 1.2 原理解读1.2.1 分布式数据存储1.2.2 存储角色1.2.3 数据高可用 1.3 小结 1.分布式文件系统DFS 学习目标&#xff1a;这一节&#xff0c;我们从DFS简介、原理…

《MATLAB科研绘图与学术图表绘制从入门到精通》

解锁MATLAB科研绘图魅力&#xff0c;让数据可视化成为你的科研利器&#xff01; 1.零基础快速入门&#xff1a;软件操作实战案例图文、代码结合讲解&#xff0c;从入门到精通快速高效。 2.多种科研绘图方法&#xff1a;科研绘图基础变量图形极坐标图形3D图形地理信息可视化等&a…

JavaWeb前端基础(HTML CSS JavaScript)

本文用于检验学习效果&#xff0c;忘记知识就去文末的链接复习 1. HTML 1.1 HTML基础 结构 头<head>身体<body> 内容 图片<img>段落<p>图标<link> 标签 单标签双标签 常用标签 div&#xff1a;分割块span&#xff1a;只占需要的大小p&…

Linux网卡IP地址配置错误的影响

在Linux系统中&#xff0c;网络配置是保持系统顺畅运行的关键一环。正确配置网卡的IP地址对于确保网络通信的准确性和效率至关重要。然而&#xff0c;如果在这个过程中发生错误&#xff0c;可能会带来一系列问题。让我们一起探讨一下&#xff0c;如果Linux网卡的IP地址配置错误…

nVisual软件在企业运维管理中的价值

​ 企业运维常见问题 1. 设备进出机房&#xff0c;上架下架一直使用excel表格记录&#xff0c;无法持续跟踪机柜设备变化&#xff0c;对机房内设备管理无法做到全过程记录&#xff1b; 2. 资料分散缺乏统一管理&#xff0c;表格手工记录容易产生遗漏&#xff0c;资料不准确&a…

小核引导RTOS---RISC-V C906

文章目录 参考日志编译框架目标fip 启动流程fip文件组成BL2程序 总结思考备注 参考 参考1. How does FSBL load the FreeRTOS on the small core and execute it?参考2. Duo now supports big and little cores?Come and play!Milk-V Duo, start&#xff01;参考3. 使用uboo…

【御控物联】JavaScript JSON结构转换(16):对象To数组——综合应用

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

【JavaSE零基础】00-基础语法(1-12章)

1 第一章 Java开发环境搭建 1.1 章节目标与知识框架 1.1.1 章节目标 掌握Java的开发环境搭建&#xff0c;会编写HelloWorld程序&#xff0c;并能够准确的进行编译和运行&#xff1b;理解path和classpath环境变量并可以自行配置。 1.1.2 知识框架 1.2 Java语言概述(了解) J…

20240404这个数字有什么特点吗?

今天是2024年的清明节&#xff0c;20240404这个数字让我提出了一个疑问&#xff0c;它是否有什么含义或者特点呢&#xff1f; 首先&#xff0c;如果把它拆分为两个整数的平方和&#xff0c;会怎么样呢&#xff1f; 于是&#xff0c;我一顿操作猛如虎&#xff0c;搞出了这么个…

如何在Python中将HTML实体代码转换为文本

在处理HTML数据时&#xff0c;有时会遇到HTML实体代码&#xff0c;这些代码是为了在HTML中表示特殊字符而使用的。例如&#xff0c;<表示小于符号(<)&#xff0c;>表示大于符号(>)&#xff0c;&表示和符号(&)等等。那么当我们在实际操作中可能会遇到下面的…

并发编程BlockingQueue、BlockingDeque

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 BlockingQueue 也叫做阻塞队列,在某些情况下对BlockingQueue的访问可能会…

Kubernetes学习笔记7

使用kubeadm部署Kubernetes集群方法 使用kubernetes部署单节点Master节点K8s集群。 在实际生产环境中&#xff0c;是不允许单master节点的&#xff0c;如果单master节点不可用的话&#xff0c;当导致我们的K8s集群无法访问。 可以使用kubeadm将单master节点升级为多master节点…

【计算机毕业设计】企业员工信息管理系统——附系统源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…

[AIGC] Spring Interceptor 拦截器详解

文章目录 什么是Spring Interceptor如何使用Spring InterceptorSpring Interceptor的影响 什么是Spring Interceptor Interceptor&#xff08;拦截器&#xff09;是Spring MVC框架中的一种特性&#xff0c;类似于Servlet开发中的Filter&#xff08;过滤器&#xff09;&#xf…