【一百零八】【算法分析与设计】P1908 逆序对,P1637 三元上升子序列,树状数组区间和应用

P1908 逆序对

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj
i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6 5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

求解逆序对的个数,按照每一个下标对应元素,以这个元素为二元组前者所拥有的逆序对的个数.
二元组意思是下标{i,j}组合,并且满足i<j.这样作为一个二元组.arr数组中有1~n下标的元素,遍历所有位置的元素,考虑i位置元素作为二元组中,下标较小的一个,看这样的元素构成的逆序对有多少个.

在这里插入图片描述
分别考虑0下标作为二元组较小下标,这样的所有二元组中的逆序对的个数.再考虑1下标作为二元组较小下标,这样的所有二元组中的逆序对的个数,依次类推,
很容易发现这样的详略可以不重不漏遍历所有的情况.

考虑i位置元素作为二元组较小下标,此时要求逆序对的个数,我们需要直到下标i+1~n区间中所有元素值小于i位置元素值的个数,个数就是逆序对的个数.
在这里插入图片描述
构建元素值映射个数的结构,只需要遍历<=i-1的前缀和即可.
很容易发现元素值映射个数结构,元素值可能是负数,并且不需要12345678连续的不少的元素值映射个数.
如果arr数组分别是145563,我们发现2元素值一直都没出现,所以2映射数量是可以不需要的.
因此需要做离散化操作.

在这里插入图片描述

离散化操作,元素值按照从小到大排序并且去重,元素值映射下标,下标从1开始,依次对应.
只需要利用map结构,将所有的元素值加入map中,然后遍历map依次赋值second为1,2,3,…以此类推.
这样我们只需要构建index映射数量的结构,index一定是正数,并且是连续的.

从后往前遍历i位置元素,查询逆序对个数,arr[i]映射index,1~index-1的前缀和,得到的就是逆序对的个数.
更新index位置的个数.以此类推.

动态维护单点更新和区间和操作,利用树状数组.

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define endl '\n'

int n; // 定义一个全局变量 n,表示序列中的数目
vector<int> arr; // 定义一个全局向量 arr,用来存储输入的序列
int ret; // 定义一个全局变量 ret,用来存储逆序对的数量
map<int, int> arr_index; // 定义一个全局 map,用来存储序列中每个数字的索引

// 树状数组类定义
class Tree {
public:
    vector<int> tree; // 定义一个向量 tree,用来存储树状数组

    // 计算 lowbit
    int lowbit(int i) {
        return i & -i; // 返回 i 和 -i 的按位与,获取最低位的 1
    }

    // 在树状数组中增加值
    void add(int i, int k) {
        while (i <= n) { // 从索引 i 开始,向上更新树状数组
            tree[i] += k; // 增加 k
            i += lowbit(i); // 移动到下一个需要更新的位置
        }
    }

    // 计算前缀和
    int sum(int i) {
        int ret = 0; // 初始化结果为 0
        while (i > 0) { // 从索引 i 开始,向下计算前缀和
            ret += tree[i]; // 加上当前索引的值
            i -= lowbit(i); // 移动到下一个需要计算的位置
        }
        return ret; // 返回前缀和
    }

    // 计算区间和
    int range(int l, int r) {
        return sum(r) - sum(l - 1); // 返回区间 [l, r] 的和
    }

    // 默认构造函数
    Tree() {}

    // 使用给定数组构造树状数组
    Tree(vector<int> arr) {
        tree.assign(arr.size(), 0); // 初始化树状数组
        for (int i = 1; i <= arr.size(); i++) {
            add(i, arr[i]); // 将数组中的值添加到树状数组中
        }
    }
};

Tree t1; // 定义一个全局的树状数组对象

// 主解题函数
void solve() {
    ret = 0; // 初始化逆序对数量为 0
    t1.tree.assign(n + 5, 0); // 初始化树状数组的大小
    int index = 1; // 初始化索引为 1
    for (auto& xx : arr_index) {
        xx.second = index++; // 给每个数字分配一个唯一的索引
    }
    for (int i = n; i >= 1; i--) {
        int index = arr_index[arr[i]]; // 获取当前数字的索引
        ret += t1.sum(index - 1); // 计算比当前数字小的数字的数量
        t1.add(index, 1); // 在树状数组中增加当前数字
    }
    cout << ret << endl; // 输出逆序对数量
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 快速输入输出

    cin >> n; // 读取序列的长度
    arr.assign(n + 5, 0); // 初始化序列数组
    for (int i = 1; i <= n; i++) {
        cin >> arr[i]; // 读取序列中的每个数字
        arr_index[arr[i]]; // 在 map 中记录每个数字
    }
    solve(); // 调用解题函数
}

P1637 三元上升子序列

三元上升子序列

题目描述

Erwin 最近对一种叫 thair 的东西巨感兴趣。。。

在含有 n n n 个整数的序列 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an 中,三个数被称作thair当且仅当 i < j < k i<j<k i<j<k
a i < a j < a k a_i<a_j<a_k ai<aj<ak

求一个序列中 thair 的个数。

输入格式

开始一行一个正整数 n n n,

以后一行 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an

输出格式

一行一个整数表示 thair 的个数。

样例 #1

样例输入 #1

4 2 1 3 4

样例输出 #1

2

样例 #2

样例输入 #2

5 1 2 2 3 4

样例输出 #2

7

提示

样例2 解释

7 7 7thair 分别是:

  • 1 2 3
  • 1 2 4
  • 1 2 3
  • 1 2 4
  • 1 3 4
  • 2 3 4
  • 2 3 4
数据规模与约定
  • 对于 30 % 30\% 30% 的数据 保证 n ≤ 100 n\le100 n100
  • 对于 60 % 60\% 60% 的数据 保证 n ≤ 2000 n\le2000 n2000
  • 对于 100 % 100\% 100% 的数据 保证 1 ≤ n ≤ 3 × 1 0 4 1 \leq n\le3\times10^4 1n3×104 1 ≤ a i ≤ 1 0 5 1\le a_i\leq 10^5 1ai105

递增三元组,遍历每一个位置元素i,i作为三元组最右侧的k下标,最大的下标,此时拥有的递增三元组的个数.
等价于求1~i-1位置小于我当前位置元素值的递增二元组的个数.
如果一个数组存储1~i-1映射二元组个数,只需要i求前缀和.

维护二元组数组,遍历每一个位置元素i,作为二元组较大的下标,此时拥有的二元组的个数是1~i-1中有多少个比我小的元素个数.
利用上一道题的思路可以利用数组数组求1~i-1中有多少比我小的元素个数.

#include<bits/stdc++.h>
using namespace std;

#define int long long // 定义 int 为 long long 类型,方便处理大数
#define endl '\n' // 定义换行符为 endl,方便输出

int n; // 定义全局变量 n,表示序列中的数目
vector<int> arr; // 定义全局向量 arr,用于存储输入的序列
int ret; // 定义全局变量 ret,用于存储三元上升子序列的数量
map<int, int> arr_index; // 定义全局 map,用于存储序列中每个数字的索引

// 树状数组类定义
class Tree {
public:
    vector<int> tree; // 定义一个向量 tree,用于存储树状数组

    // 计算 lowbit
    int lowbit(int i) {
        return i & -i; // 返回 i 和 -i 的按位与,获取最低位的 1
    }

    // 在树状数组中增加值
    void add(int i, int k) {
        while (i <= n) { // 从索引 i 开始,向上更新树状数组
            tree[i] += k; // 增加 k
            i += lowbit(i); // 移动到下一个需要更新的位置
        }
    }

    // 计算前缀和
    int sum(int i) {
        int ret = 0; // 初始化结果为 0
        while (i > 0) { // 从索引 i 开始,向下计算前缀和
            ret += tree[i]; // 加上当前索引的值
            i -= lowbit(i); // 移动到下一个需要计算的位置
        }
        return ret; // 返回前缀和
    }

    // 计算区间和
    int range(int l, int r) {
        return sum(r) - sum(l - 1); // 返回区间 [l, r] 的和
    }

    // 默认构造函数
    Tree() {}

    // 使用给定数组构造树状数组
    Tree(vector<int> arr) {
        tree.assign(arr.size(), 0); // 初始化树状数组
        for (int i = 1; i <= arr.size(); i++) {
            add(i, arr[i]); // 将数组中的值添加到树状数组中
        }
    }
};

Tree t1, t2; // 定义两个全局的树状数组对象

// 主解题函数
void solve() {
    t1.tree.assign(n + 5, 0); // 初始化第一个树状数组的大小
    t2.tree.assign(n + 5, 0); // 初始化第二个树状数组的大小

    int index = 1; // 初始化索引为 1
    for (auto& xx : arr_index) {
        xx.second = index++; // 给每个数字分配一个唯一的索引
    }

    ret = 0; // 初始化三元上升子序列的数量为 0
    for (int i = 1; i <= n; i++) {
        int index = arr_index[arr[i]]; // 获取当前数字的索引
        t1.add(index, 1); // 在第一个树状数组中增加当前数字
        t2.add(index, t1.sum(index - 1)); // 在第二个树状数组中增加当前数字左侧比它小的数字的数量
        ret += t2.sum(index - 1); // 累加比当前数字小的数字的数量,得到三元上升子序列的数量
    }
    cout << ret << endl; // 输出三元上升子序列的数量
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 快速输入输出

    cin >> n; // 读取序列的长度
    arr.assign(n + 5, 0); // 初始化序列数组
    for (int i = 1; i <= n; i++) {
        cin >> arr[i]; // 读取序列中的每个数字
        arr_index[arr[i]]; // 在 map 中记录每个数字
    }
    solve(); // 调用解题函数
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】海康威视综合安防管理平台 多处 FastJson反序列化RCE漏洞

0x01 产品简介 海康威视综合安防管理平台是一套“集成化”、“智能化”的平台,通过接入视频监控、一卡通、停车场、报警检测等系统的设备。海康威视集成化综合管理软件平台,可以对接入的视频监控点集中管理,实现统一部署、统一配置、统一管理和统一调度。 0x02 漏洞概述 由于…

在Cisco Packet Tracer上配置NAT

目录 前言一、搭建网络拓扑1.1 配置PC机1.2 配置客户路由器1.3 配置ISP路由器 二、配置NAT2.1 在客户路由器中配置NAT2.2 测试是否配置成功 总结 前言 本篇文章是在了解NAT的原理基础上&#xff0c;通过使用Cisco Packet Tracer 网络模拟器实现模拟对NAT的配置&#xff0c;以加…

源码、反码和补码

对于有符号数而言&#xff0c;原码就是一个数的二进制表示。二进制的最高位是符号位&#xff0c;0 表示正数&#xff0c;1 表示负数。 计算机用数的原码进行显示&#xff0c;数的计算和存储是用补码进行的。 正数的原码&#xff0c;反码和补码都一样&#xff0c;即正数三码合…

Cesium中的坐标系统简单说明(2026-06-08)

一、坐标系统 cesium中坐标系统分为地理坐标、世界坐标&#xff08;X,Y,Z&#xff09;、屏幕坐标三种。 通常使用地理坐标来进行位置表达&#xff0c;笛卡尔空间坐标系常用来做一些空间位置变换如平移旋转缩放&#xff0c;屏幕坐标常用来做用户交互等&#xff0c;三者之间相互…

win7补丁下载

目的 一般来说&#xff0c;安装上windows系统就带着补丁了&#xff0c;但有时&#xff0c;安装的是原始版的操作系统是不带补丁的&#xff0c;一般直接更新就可以了&#xff0c;但有时&#xff0c;电脑不能联网&#xff0c;只能通过安装包进行升级&#xff0c;所以下面介绍如何…

Docker 学习总结(83)—— 配置文件daemon.json介绍及优化建议

一、daemon.json 文件概述 daemon.json是Docker守护进程的配置文件,它允许系统管理员自定义Docker守护程序的行为。此文件通常位于/etc/docker/目录下。通过修改daemon.json,可以调整Docker守护进程的多种设置,包括网络配置、日志记录、存储驱动等。 二、daemon.json 文件结…

【Vue】路由的基本使用

文章目录 一、固定5个固定的步骤二、代码示例三、两个核心步骤四、完整代码 vue-router插件作用 修改地址栏路径时&#xff0c;切换显示匹配的组件 说明 Vue 官方的一个路由插件&#xff0c;是一个第三方包 官网 https://v3.router.vuejs.org/zh/ VueRouter的使用&#xff0…

【安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务】

安装笔记-系列文章目录 安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务 文章目录 安装笔记-系列文章目录安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务 前言一、软件介绍名称&#xff1a;acme.sh主页官方介绍 二、安装步骤测试版本&#x…

React Hooks 封装可粘贴图片的输入框组件(wangeditor)

需求是需要一个文本框 但是可以支持右键或者ctrlv粘贴图片&#xff0c;原生js很麻烦&#xff0c;那不如用插件来实现吧~我这里用的wangeditor插件&#xff0c;初次写初次用&#xff0c;可能不太好&#xff0c;但目前是可以达到实现需求的一个效果啦&#xff01;后面再改进吧~ …

两张图片进行分析

两张图片进行分析&#xff0c;可以拖动左边图片进行放大、缩小查看图片差异 底图 <template><div class"box_container"><section><div class"" v-for"item in imgData.imgDataVal" :key"item.id"><img :s…

JavaSE--【类和对象】

本篇目标 1. 掌握类的定义方式以及对象的实例化 2. 掌握类中的成员变量和成员方法的使用 3. 掌握对象的整个初始化过程 一、面向对象的初步认知 1.1 面向对象的初步认知 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里…

Execl数据导入 EasyExcel实现

官网 1. 需求简介 读取下面表格数据 第一行和第二行是计划信息 第三行是计划详情的抬头信息,以下行是计划详情信息 总段包含多个分段,总段使用了单元格合并功能 2. 实现读取功能 2.1 引入easyexcel依赖 <dependency><groupId>com.alibaba</groupId><…

移动端 UI 风格,视觉盛宴

移动端 UI 风格&#xff0c;视觉盛宴

10.dockerfile自动构建镜像

dockerfile自动构建镜像 类似ansible剧本&#xff0c;大小几kb 手动做镜像&#xff1a;大小几百M 首先创建一个dockerfile的路径&#xff0c;便于在路径下存在多个路径每个路径下都是dockerfile命名的脚本 注释&#xff1a;文件必须为&#xff1a;dockerfile或者Dockerfile …

QT中为程序加入超级管理员权限

QT中为程序加入超级管理员权限 Chapter1 QT中为程序加入超级管理员权限1. mingw编译器2. MSVC编译器3. CMAKE Chapter2 如何给QT程序添加管理员权限(UAC)的几种方法1、Qt Creator中方案一&#xff1a;&#xff08;仅适用于使用msvc编译器&#xff09;方案二&#xff1a;&#x…

单链表复习 (C语言版)

目录 一.顺序表与链表的区别 二.链表概念 三.单链表 1.单链表的开始与初始化 2.单链表的打印 3.单链表的尾插 重难点&#xff1a;单链表实现时的指针详解 4.单链表的头插 5.单链表的尾删 6.单链表的头删 小结&#xff1a; 7.单链表的查找 8.在指定位置前插入数据 …

制作AI问答机器人:从0到1的完整指南

在数字化转型的浪潮中&#xff0c;企业正追求更高效、智能的客户服务解决方案。AI问答机器人以其快速响应、全天候服务和持续学习的能力&#xff0c;成为了提升客户满意度和加速业务发展的关键工具。本文将深入探讨如何制作一个企业级的AI问答机器人&#xff0c;并强调其功能体…

【Linux】(五)—— SSH远程登录和XShell使用

SSH Linux中的SSH&#xff08;Secure Shell&#xff09;是一个强大的网络协议&#xff0c;用于在不安全的网络环境中提供安全的远程登录和资料拷贝等其他网络服务。以下是有关Linux中SSH的关键点和操作指南&#xff1a; SSH的基础概念 安全性&#xff1a;SSH通过对所有传输的…

编程规范-代码检测-格式化-规范化提交

适用于vue项目的编程规范 – 在多人开发时统一编程规范至关重要 1、代码检测 --Eslint Eslint&#xff1a;一个插件化的 javascript 代码检测工具 在 .eslintrc.js 文件中进行配置 // ESLint 配置文件遵循 commonJS 的导出规则&#xff0c;所导出的对象就是 ESLint 的配置对…

【Pytorch】一文向您详细介绍 torch.Tensor() 的常见用法

【Pytorch】一文向您详细介绍 torch.Tensor() 的常见用法 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通…