权值线段树 详解+操作模板(c++)

news2024/12/23 3:18:42

文章目录

  • 权值线段树
    • 添加一个数字
    • 求某数出现的次数
    • 查询一段区间中数字出现的次数
    • 查询整个值域中第k小的数
    • 查询整个值域中第k大的数
    • 例子:求逆序对

关于基本线段树与线段树的模板,请看我们之前发布的博客:
线段树入门详解
维护加法乘法,区间修改查询的线段树模板

请注意,本节的前置知识是必须懂得基础线段树的操作。


权值线段树

什么是权值线段树,顾名思义就是节点带权值的线段树,我们基本的线段树的节点由一个区间范围和一个记录最大最小值或者区间和的数值组成。 但是我们的权值线段树的节点是由权值构成的,即数组中出现的某一个数字的次数,就可以看作一个权值,因此就可以存储在权值线段树中当作这个线段的节点,这就是权值线段树。

对于一个给定的数组:

  • 普通线段树可以维护某个子数组中数的和
  • 而权值线段树可以维护某个区间内数组元素出现的次数。

请注意:权值线段树维护的是值域,对于权值线段树来说空间往往是其限制因素,对于le9以上的数据,我们基本不可能做到维护1e9以上的数据,因此往往需要 离散化+线段树,不光是权值线段树,基本上所有的线段树都需要离散数据,如果值域小的话则不需要。

关于离散化数据的n种方式:数据的离散化方式

权值线段树对于处理值域上的值出现的次数,即计数问题有着很大的优势。


权值线段树的作用领域有哪些???

  1. 求一段区间的某个数字出现的次数
  2. 查询整体区间的第k大/小的值(注意是整体区间(整个值域),等会你就会发现权值线段树与主席树的区别,主席树可以求得任意区间的第k大/小的值)

添加一个数字

往权值线段树中添加一个数字,则节点记录的就是这个数字出现的次数,因此递归到指定区间后,次数加1即可。

//添加数字
void update(int i, int pl, int pr, int x)
{
	if (pl == pr)
	{
		sum[i]++;	//到达了叶子节点,叶子节点维护的就是这个数字出现的次数
		return;
	}
	int mid = (pl + pr) >> 1;
	if (x <= mid) update(i << 1, pl, mid, x);
	if (x > mid) update(i << 1 | 1, mid + 1, pr, x);
	sum[i] = sum[i << 1] + sum[i << 1 | 1];
}

测试代码如下:结果肯定是没有问题的,每添加一个数字,每一个叶子节点就会更新为这个值出现的次数,根节点表示这个这个值域中的数字出现的次数

build(0,1,10);
for (int i = 1; i <= 10; i++)
{
	update(1, 1, 10, i);
	for (int i = 1; i <= 30; i++)
	{
		cout << sum[i] << " ";
	}
	cout << endl;
}

在这里插入图片描述


求某数出现的次数

  • 递归寻找表示此数的区间
  • pl==pr表示到达叶子节点,返回sum[i]即可
int query(int i, int pl, int pr,int x)
{
	//x表示要查询的值
	if (pl == pr)
	{
		return sum[i];
	}
	int ans = 0;
	int mid = (pl + pr) >> 1;
	if (x <= mid) ans = query(i << 1, pl, mid, x);
	if (x > mid) ans = query(i << 1 | 1, mid + 1, pr, x);
	return ans;
}

查询一段区间中数字出现的次数

  • 给出一段区间: [L,R] 为 [1,5] ,在权值线段树中查询 在这个区间里的所有的数出现的总次数,即1,2,3,4,5在权值线段树中从共出现了多少次
  • 递归左右子树,找到完全覆盖的子区间
  • ans要 +=,否则会在一次查找后把ans原值覆盖。
int query(int i, int pl, int pr, int L, int R)
{
	//[L,R]表示要查询的区间
	if (L <= pl && pr <= R)
	{
		return sum[i];
	}
	int ans = 0;
	int mid = (pl + pr) >> 1;
	if (L <= mid) ans += query(i << 1, pl, mid, L, R);
	if (R > mid) ans += query(i << 1 | 1, mid + 1, pr, L, R);
	return ans;
}

查询整个值域中第k小的数

int query2(int i, int pl, int pr, int k)
{
	/*
	第k小的数:
		首先求出左右孩子节点的元素个数 Ln=sum[i<<1] Rn=sum[i<<1|1]
		1. 如果k小于等于Ln,说明第k小的元素在左子树中,则递归到左子树
		2. 如果k大于Ln,说明第k小的数字在右子树中,则递归到右子树,同时注意如果k=8,左子树元素有5个,则在右子树中相当于寻找第 k-Ln 个,即第3个元素
	*/
	if (pl == pr)
	{
		return pl;
	}
	int ans = 0;
	int mid = (pl + pr) >> 1;
	int Ln = sum[i << 1];	//左孩子表示的元素个数
	int Rn = sum[i << 1 | 1];//右孩子表示的元素个数
	if (k <= Ln) ans = query2(i << 1, pl, mid, k);		//左子树
	else ans = query2(i << 1 | 1, mid + 1, pr, k - Ln);	//右子树
	return ans;
}

查询整个值域中第k大的数

注意:我们要求的是整个值域中,而不是任意区间。

int query3(int i, int pl, int pr, int k)
{
	/*
	第k大的数: 相当于逆着求第k小的数
		首先求出左右孩子节点的元素个数 Ln=sum[i<<1] Rn=sum[i<<1|1]
		1. 如果k小于等于Rn,则第k大的元素在右子树中,递归右子树
		2. 如果k大于Rn,则第k大元素在左子树中,同时注意k=4 右子树元素有3个,则在左子树中相当于寻找第 k-Rn 个元素,即寻找第1个元素
	*/
	if (pl == pr)
	{
		return pl;
	}
	int ans = 0;
	int mid = (pl + pr) >> 1;
	int Ln = sum[i << 1];	 //左孩子表示的元素个数
	int Rn = sum[i << 1 | 1];//右孩子表示的元素个数
	if (k <= Rn) ans = query3(i << 1|1, mid+1, pr, k);	//右子树	
	else ans = query3(i << 1, pl, mid, k - Rn);			//左子树
	return ans;
}

注意:值线段树的每一个节点从左往右满足递增,因此较大的元素一定在右子树中,较小的元素一定在左子树中


例子:求逆序对

题目传送门:逆序对

对于题目我不再描述,我们可以知道,逆序对的几种做法:

  1. 归并排序
  2. 树状数组
  3. 线段树(权值线段树)

注意:用权值线段树求逆序对相对比树状数组和归并排序很慢!!!!!!!!!!

本例只是为了演示权值线段树的用法!!


思路

  1. 离散化数据,然后把离散化后的数据逐一添加到权值线段树中
  2. 每次添加完成后,求 nums[i]+1 - N 这个范围内的元素的个数,比nums[i]大,即求得是这个nums[i] 的逆序对的数量。

用到了上面的 《查询一段区间中数字出现的次数》的代码


AC code

注意:在权值线段树中build函数可有可无,我们主要是利用update添加一个元素

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using DB = double;
using PI = pair<int, int>; 
using PL = pair<LL, LL>;
template<typename T> using v = vector<T>;
constexpr auto INF = 0X3F3F3F3F;
template<typename T1,typename T2> using umap = unordered_map<T1, T2>;
#define ic std::ios::sync_with_stdio(false);std::cin.tie(nullptr)
template <typename ConTainermap> void dbgumap(ConTainermap c);	//output umap
template <typename _Ty> void dbg(_Ty nums[],int n);				
#if 1
	#define int LL
#endif
inline int read();			//fast input
inline void write(int x);	//fast output

//TODO: Write code here
int n;
const int N=5e5+10;
int nums[N<<2],tree[N<<2],temp[N<<2];
void build(int i,int pl,int pr)
{
	if (pl==pr)
	{
		tree[i]=0;	//元素出现的次数++
		return;
	}
	int mid=(pl+pr)>>1;
	build(i<<1,pl,mid);
	build(i<<1|1,mid+1,pr);
	tree[i]=tree[i<<1]+tree[i<<1|1];
}
//更新权值线段树: 添加元素
void update(int i,int pl,int pr,int num)
{
	if (pl==pr)
	{
		tree[i]++;	//元素出现的次数++
		return;
	}
	int mid=(pl+pr)>>1;
	if (num<=mid) update(i<<1,pl,mid,num);
	if (num>mid) update(i<<1|1,mid+1,pr,num);
	//push_up更新
	tree[i]=tree[i<<1]+tree[i<<1|1];
}
//查询一段区间中数字出现的次数
int query(int i,int pl,int pr,int L,int R)
{
	if (L<=pl && pr<=R)
	{
		return tree[i];	//找到此元素节点
	}
	int ans=0;
	int mid=(pl+pr)>>1;
	if (L<=mid) ans+=query(i<<1,pl,mid,L,R);
	if (R>mid) ans+=query(i<<1|1,mid+1,pr,L,R);
	return ans;
}
signed main()
{
	cin>>n;
	for (int i=1;i<=n;i++)
	{	
		cin>>nums[i];
		temp[i]=nums[i];
	}
	sort(temp+1,temp+1+n);
	int len=unique(temp+1,temp+1+n)-temp-1;
	build(1,1,N);
	int ans=0;
	for (int i=1;i<=n;i++)
	{
		nums[i]=lower_bound(temp+1,temp+1+len,nums[i])-temp;
		//每次添加一个元素到权值线段树中
		update(1,1,N,nums[i]);	
		ans+=query(1,1,N,nums[i]+1,N);
	}
	cout<<ans;
	return 0;
}
template <typename _Ty>
void dbg(_Ty nums[],int n)
{
	for (int i=1;i<=n;i++)
	{
		cout<<nums[i]<<" ";
	}
	cout<<endl;
}
template <typename ConTainermap>
void dbgumap(ConTainermap c)
{
	for (auto& x:c)
	{
		cout<<"key:"<<x.first<<"  val:"<<x.second<<endl;
	}
}
inline int read() 
{
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9')
		{ 
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{ 
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}
inline void output(int x)
 {
	static int sta[35];
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	while (top) putchar(sta[--top] + 48);
}

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

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

相关文章

关于“茴香豆的‘茴’有几种写法”:学习过程中,若时间精力有限则优先记住最好用的一种

学习过程中的细节整理和精力节省权衡 我平时学习有整理总结、记笔记的习惯。 我学新东西总是很慢&#xff0c;因为细节处几乎都不会放过&#xff0c;会去发散&#xff0c;去深挖&#xff0c;去比较之前。 刚才上网&#xff0c;查了C语言中二维数组的赋值方式&#xff0c;某个…

UVM实战笔记(七)

第七章. UVM中的寄存器模型 7.1 寄存器模型简介 7.1.1 带寄存器配置总线的DUT 本章节使用的DUT带寄存器配置&#xff0c;代码如下&#xff1a; module dut(clk,rst_n,bus_cmd_valid,bus_op,bus_addr,bus_wr_data,bus_rd_data,rxd,rx_dv,txd,tx_en)input clk; …

DaVinci:Camera Raw(Sony RAW)

本文主要介绍 Sony RAW 格式素材相关的 Camera Raw 参数。解码质量Decode Quality解码质量决定了图像解拜耳之后所呈现的素质。默认为“使用项目设置” Use project setting&#xff0c;表示使用项目设置对话框中的“Camera RAW”解码质量设置。还可选择&#xff1a;全分辨率 -…

JavaEE-网络编程

目录一、网络编程套接字二、UDP Socket2.1 客户端服务器程序-回显服务(EchoServer)2.1.1 UdpEchoServer2.1.2 UdpEchoClient2.1.3 一个简单程序三、TCP 客户端服务器程序3.1 TCP API一、网络编程套接字 网络编程套接字就是操作系统给应用程序提供的一组API(叫做socket API)。 …

NLP学习笔记(七) BERT简明介绍

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲 BERT\text{BERT}BERT (Bidirectional Encoder Representations from Transformers) 原始论文请戳这里 0 概述 从某种程度上来说&#xff0c;深度学习至关重要的一环就是表征学习&#xff0c;也就是学习如何得到数据的向…

怎么把两个PDF合并?教你们几个简单的方法

不知道大家平时处理文件的数量多不多&#xff0c;但是小编日常处理文件真的特别多&#xff0c;所以小编经常会使用专业的格式转换器来处理文件&#xff0c;这样就可以高效处理文件了&#xff0c;例如我们需要将多个PDF文件合并&#xff0c;这样就只需要传输一个文件就可以了&am…

自定义starter解决请求绕过网关问题

引言 微服务项目中网关是一个常见的模块&#xff0c;通过网关的分发可以实现负载均衡、鉴权等操作&#xff1b;但是搭建好网关可以发现&#xff0c;虽然可以通过网关端口请求后端&#xff0c;如果有其他服务的地址依然可以使用其他服务地址绕过网关请求&#xff0c;这里我提供…

利用RadminLan和TcpRoute2将工作带回家

需要准备的工具 1.RadminLan 下载地址–>https://www.radmin-lan.cn/ 2.TcpRoute2 项目地址–>https://github.com/GameXG/TcpRoute2 *选用&#xff1a;浏览器插件proxy-switchyomega&#xff1a;https://microsoftedge.microsoft.com/addons/detail/proxy-switchyomega…

Visual Studio Code 的安装和使用

Visual Stuio Code 微软出的一款免费编辑器。 有 Windows、Linux 和macOS 三种版本的&#xff0c;属于跨平台的编辑器。它功能强大&#xff0c;支持插件工具安装&#xff0c;对于写代码、阅读代码的人来说是非常方便的。 1、安装 Visual Stuio Code 下载地址如下&#xff1a; h…

win10修改jdk版本之后不生效的有效解决方法

问题起因今天学习seata的时候&#xff0c;启动seata服务发现启动不了报下图错误。发现是自己jdk版本太高了&#xff0c;现在我用的是jdk17。然后我修改jdk的环境变量&#xff0c;确定保存好。发现jdk的版本还是没有变化。问题原因当使用安装版本的JDK程序时&#xff08;一般是1…

jmeter 并发测试

1.右键测试计划(Test plan), 添加线程组 2.线程组配置 3.右键线程组, 添加取样器-HTTP请求 4.HTTP请求配置 5. 添加查看结果树(也可以在Test plan 测试计划上右键添加)

Grafana 系列文章(二):使用 Grafana Agent 和 Grafana Tempo 进行 Tracing

&#x1f449;️URL: https://grafana.com/blog/2020/11/17/tracing-with-the-grafana-cloud-agent-and-grafana-tempo/ ✍Author: Robert Fratto • 17 Nov 2020 &#x1f4dd;Description: Heres your starter guide to configuring the Grafana Agent to collect traces and…

【刷题】多数元素

这是leetcode第169题的解答。 目录 一、多数元素 二、实现思路 1.排序中间下标求众数 2.投票法 总结 一、多数元素 多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 二、实现思路 1.排序中间下标求众数 原理&#xff1a; 通过排序使得数组有序&#xff0c;因为多数元素…

ESP32设备驱动-TM1637-驱动4位7段数码管

TM1637-驱动4位7段数码管 1、TM1637介绍 TM1637是一款带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成了MCU数字接口、数据锁存、LED高压驱动、键盘扫描等功能。 TM1637使用DIP20/SOP20封装,主要适用于电磁炉、微波炉、小家电的显示驱动。 TM1637有如下…

【C++】初识C++

本期博客我们来正式进入到期待已久C嘎嘎的学习希望C语言以后别给我打电话了&#xff0c;我怕C误会&#x1f63c;一、认识C1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的 程序&#xff0c;需要高度的抽象和…

蓝奥声无线单火控制技术在单火开关应用中的优势

随着科技的发展&#xff0c;智能产品在生活中越来越常见&#xff0c;为方便业主使用&#xff0c;就连开关也有了高阶智能版&#xff0c;据相关专家介绍&#xff0c;智能开关主要分为单火和零火两种&#xff0c;很多非专业人士搞不明白&#xff0c;但又害怕因此选择失误。那么&a…

关于微服务的一些总结和经验之谈,来看看你都了解吗

文章目录一 谈谈对微服务的理解1. 什么微服务&#xff1f;2. 微服务体系3. 微服务优点4. 微服务缺点5. 什么是gRPC&#xff1f;6. ProtoBuf协议好处&#xff1f;7. gPRC和ProtoBuf联系&#xff1f;二 本次微服务项目学习流程梳理三 微服务项目一般开发流程梳理四 从本次微服务项…

数据结构 | 图结构的讲解与模拟实现 | DFS与BFS的实现

文章目录前言常见概念总结图的模拟实现邻接矩阵和邻接表的优劣图的模拟实现&#xff08;邻接表&#xff09;广度优先遍历&#xff08;BFS&#xff09;深度优先遍历&#xff08;DFS&#xff09;前言 在聊图的结构之前&#xff0c;我们可以先从熟悉的地方开始&#xff0c;这有一…

Leetcode 剑指 Offer II 012. 左右两边子数组的和相等

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 …

Android 启动速度优化

Android 启动速度优化前序统计adb测量手动打点方案预加载class字节码的预加载Activity预创建Glide预初始化WebView预加载数据预加载三方库初始化布局方面ViewStub标签减少层级主题的选择约束布局使用X2C方案过度绘制如何检测过度绘制如何监控页面的渲染速度移除多余背景Recycle…