牛客——火柴排队(树状数组与归并排、逆序对)

news/2024/5/17 17:12:48 标签: 算法, 树状数组, 逆序对, 归并排序

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为: ∑(ai−bi)2\sum (a_i-b_i)^2∑(ai​−bi​)2
其中 ai 表示第一列火柴中第 i 个火柴的高度, bi 表示第二列火柴中第 i 个火柴的高度。
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。

输入描述:

共三行,第一行包含一个整数 n ,表示每盒中火柴的数目。
第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。
第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。

输出描述:

一个整数,表示最少交换次数对 99,999,997 取模的结果。

一、归并排序的基本思想是将待排序的序列分成两个子序列,分别对两个子序列进行排序,然后将两个已排序的子序列合并成一个有序序列。通过递归地将序列分解成足够小的子序列,再将子序列两两合并,最终得到完全有序的序列。

归并排序的具体步骤如下:

  1. 分解(Divide):将待排序的序列不断二分,直到得到足够小的子序列。

  2. 解决(Conquer):对每个子序列进行排序,可以使用递归调用归并排序

  3. 合并(Merge):将两个已排序的子序列合并成一个有序序列。合并操作中,需要创建一个临时数组来存储合并后的有序序列。

归并排序的时间复杂度是 O(nlogn),其中 n 是待排序序列的长度。它的排序过程是稳定的,即相等元素的相对顺序不会改变。

归并排序可以用于排序各种类型的数据,尤其适用于链表结构。相对于其他排序算法归并排序的主要优点是在最坏情况下也能保证 O(nlogn) 的时间复杂度,因此在实际应用中被广泛使用。

合并的过程中,需要用到一个辅助数组来暂存合并后的有序数组。具体来说,假设待合并的两个有序数组分别为 A 和 B,它们的长度分别为 n 和 m,合并后的有序数组为 C,那么合并的过程可以按如下步骤进行:

1.定义三个指针 i、j 和 k,分别指向数组 A、B 和 C 的起始位置。

2.比较 A[i] 和 B[j] 的大小,将小的元素放入 C[k] 中,并将对应指针向后移动一位。

3.重复步骤 2,直到其中一个数组的元素全部放入 C 中。

4.将另一个数组中剩余的元素放入 C 中。

归并排序的优点是稳定性好,即对于相等的元素,在排序前后它们的相对位置不会改变。缺点是需要额外的空间来存储辅助数组。

二、逆序对是指在一个序列中,如果两个元素的顺序与它们在原序列中的顺序相反,即前面的元素大于后面的元素,则这两个元素构成一个逆序对

例如,在序列 [2, 4, 1, 3, 5] 中,有三个逆序对:(2, 1),(4, 1),(4, 3),因为这些元素的顺序与它们在原序列中的顺序相反。

逆序对在排序算法中具有重要的意义,它可以衡量一个序列的有序程度。对于一个完全有序的序列,逆序对数为0,而对于一个完全逆序的序列,逆序对数最大。因此,逆序对可以用来评估一个序列的有序性。

算法设计中,可以使用分治法来统计逆序对的数量。例如,可以使用归并排序算法,在归并的过程中统计逆序对的数量。具体来说,归并排序将序列分为两个子序列,然后分别对子序列进行排序,并在合并的过程中统计逆序对的数量。

代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
const int mod = 1e8 - 3;

int n;
int c[N], tmp[N];

struct Node {
    int data;
    int idx;
};

bool cmp(Node x, Node y) {
    return x.data < y.data;
}

int merge_sort(int l, int r) {
    if (l >= r) return 0;
    int mid = l + r >> 1;
    int res = (merge_sort(l, mid) + merge_sort(mid + 1, r)) % mod;
    int i = l, j = mid + 1, k = 1;
    while (i <= mid && j <= r) {
        if (c[i] <= c[j]) tmp[k++] = c[i++];
        else {
            res += mid - i + 1;
            tmp[k++] = c[j++];
        }
    }
    while (i <= mid) tmp[k++] = c[i++];
    while (j <= r) tmp[k++] = c[j++];
    for (int i = l, j = 1; i <= r; i++, j++) c[i] = tmp[j];
    return res % mod;
}

int main() {
    cin >> n;
    Node a[N], b[N];
    for (int i = 1; i <= n; i++) {
        cin >> a[i].data;
        a[i].idx = i;
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i].data;
        b[i].idx = i;
    }

    sort(a + 1, a + 1 + n, cmp);
    sort(b + 1, b + 1 + n, cmp);
    for (int i = 1; i <= n; i++)
        c[b[i].idx] = a[i].idx;

    int ans = merge_sort(1, n);
    cout << ans % mod << endl;
    return 0;
}

具体解析看这个:刷题总结——火柴排队(NOIP2013)-CSDN博客

三、树状数组

树状数组(Binary Indexed Tree,BIT),也被称为 Fenwick 树,是一种用于高效计算前缀和的数据结构。它可以在 O(logn) 的时间复杂度内完成单点更新和前缀和查询操作。

树状数组主要用于解决频繁更新数组元素、频繁查询前缀和的问题,例如求逆序对、求区间和等。它的主要思想是将原始数组按照某种方式进行压缩,从而减少查询和更新操作的时间复杂度。

树状数组的核心是一个数组,通常用 BIT 表示。数组的下标从 1 开始,对应于原始数组的元素位置。数组的每个元素存储了一部分原始数组的元素和,具体的计算方式是通过二进制的技巧来实现的。

树状数组的基本操作包括单点更新和前缀和查询:

  1. 单点更新:通过不断修改 BIT 数组的某些元素,实现对原始数组中某个位置上元素的更新。更新操作的时间复杂度为 O(logn)。

  2. 前缀和查询:通过不断累加 BIT 数组的某些元素,实现对原始数组的前缀和计算。查询操作的时间复杂度为 O(logn)。

树状数组的构建过程相对简单,首先初始化 BIT 数组为全 0,然后通过单点更新操作依次更新 BIT 数组中的元素。构建完成后,即可进行前缀和查询。

树状数组的应用非常广泛,例如在求逆序对的问题中,可以使用树状数组来统计逆序对的数量;在求区间和的问题中,可以使用树状数组来快速计算任意区间的和。

首先不难想到a序列的第k小与b序列的第k小分别对应就能达到答案要求

将序列a,b做一些类似离散化的处理
先给序列中每个数标上编号,然后按原来的权值排序

这样a,b都变成了1-n的排列
现在问题可以转化为最少将b做多少次相邻元素的交换可以与a完全相同

我们设数组c[a[i]]=b[i]

若b数组与a数组完全相同,那么c[a[i]]=a[i]

即c[i]=i

代码如下:

#include <iostream>
#include <algorithm>

#define int long long
using namespace std;

const int N = 1e6 + 5, mod = 1e8 - 3;

int n;
int c[N], tr[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

signed main() {
    cin >> n;

    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        c[x] = i;
    }

    int res = 0;

    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        int idx = c[x];
        add(idx, 1);
        res += i - sum(idx);
    }

    cout << res % mod << endl;

    return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int NN = 1e5 + 4;
const int P = 99999997;
int n;
vector<int> a(NN), b(NN), c(NN);
int lower(int x) {
    return x & (-x);
}
void add(int x) {
    while (x) {
        c[x]++;
        x -= lower(x);
    }
}
int get(int x) {
    int sum = 0;
    while (x <= n) {
        sum += c[x];
        x += lower(x);
    }
    return sum;
}
int main() {
    cin>>n;
    for (int i = 1; i <= n; i++) {
        int x;
        cin>>x;
        a[x] = i;
    }
    for (int i = 1; i <= n; i++) {
        int x;
        cin>>x;
        b[i] = a[x];
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        ans = (ans + get(b[i] + 1)) % P;
        add(b[i]);
    }
    cout<<ans;
    return 0;
}


http://www.niftyadmin.cn/n/5383575.html

相关文章

MATLAB环境下使用二维高分辨时频分析方法提取波状分量

MATLAB环境下使用二维高分辨时频分析方法提取波状分量&#xff08;分离混合地震数据&#xff09;。 为了得到更高的时频分辨率&#xff0c;近年来涌现出了大量的新的时频分析方法。有些以线性和非线性时频分析为基础&#xff0c;有些则另辟蹊径&#xff0c;比如Hilbert-Huang变…

MyBatis框架-缓存

MyBatis缓存 简介 什么是缓存 [ Cache ]&#xff1f; 存在内存中的临时数据。将用户经常查询的数据放在缓存&#xff08;内存&#xff09;中&#xff0c;用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询&#xff0c;从缓存中查询&#xff0c;从而提高查询效率&#…

html table表格动态显示

html <table class"tab-scroll" cellpadding"0" cellspacing"0" bgcolor""><thead><tr align"center"><th style"width: 50%;">问题名称</th><th style"width: 50%;&quo…

地下管线管网三维建模工具MagicPipe3D V3.4.2发布

经纬管网建模系统MagicPipe3D&#xff0c;本地离线参数化构建地下管网三维模型&#xff08;包括管道、接头、附属设施等&#xff09;&#xff0c;输出标准3DTiles服务、Obj模型等格式&#xff0c;支持Cesium、Unreal、Unity、Osg等引擎加载进行三维可视化、语义查询、专题分析&…

蓝牙攻击工具集合与使用说明

bluez - 蓝牙协议栈&#xff0c;提供了蓝牙设备管理和通信的基本功能。btscanner - 用于发现和扫描蓝牙设备的工具。bluetoothctl - 蓝牙控制终端工具&#xff0c;用于管理蓝牙设备和进行配对等操作。spooftooph - 用于欺骗和伪造蓝牙设备的MAC地址的工具。ubertooth - 用于无线…

【笔记】APN 配置参数 bitmask 数据转换(Android KaiOS)

一、参数说明 &#xff08;一&#xff09;APN配置结构对比 平台AndroidKaiOS文件类型xmljson结构每个<apn>标签是一条APN&#xff0c;包含完成的信息层级数组结构&#xff0c;使用JSON格式的数据。最外层是mcc&#xff0c;其次mnc&#xff0c;最后APN用数组形式配置&am…

Linux中信号机制

信号机制 信号的概念 概念&#xff1a;信号是在软件层次上对中断机制的一种模拟&#xff0c;是一种异步通信方式 所有信号的产生及处理全部都是由内核完成的信号的产生&#xff1a; 1 按键产生 2 系统调用函数产生&#xff08;比如raise&#xff0c; kill&#xff09; 3 硬件…

基于Java的大学社团管理平台

功能介绍 平台采用B/S结构&#xff0c;后端采用主流的Springboot框架进行开发&#xff0c;前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 前台功能包括&#xff1a;首页、社团详情、申请加入、用户中心模块。后台功能包括&#xff1a;社团管理、分类管理…