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

2024-02-20 21:20

本文主要是介绍牛客——火柴排队(树状数组与归并排、逆序对),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

链接:登录—专业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.chinasem.cn/article/729573

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

火柴游戏java版

代码 /*** 火柴游戏* <p>* <li>有24根火柴</li>* <li>组成 A + B = C 等式</li>* <li>总共有多少种适合方式?</li>* <br>* <h>分析:</h>* <li>除去"+"、"="四根,最多可用火柴根数20根。</li>* <li>全部用两根组合成"1",最大数值为1111。使用枚举法,A和B范围在0~1111,C为A+B。判断</li>** @

计算数组的斜率,偏移,R2

模拟Excel中的R2的计算。         public bool fnCheckRear_R2(List<double[]> lRear, int iMinRear, int iMaxRear, ref double dR2)         {             bool bResult = true;             int n = 0;             dou

C# double[] 和Matlab数组MWArray[]转换

C# double[] 转换成MWArray[], 直接赋值就行             MWNumericArray[] ma = new MWNumericArray[4];             double[] dT = new double[] { 0 };             double[] dT1 = new double[] { 0,2 };

PHP7扩展开发之数组处理

前言 这次,我们将演示如何在PHP扩展中如何对数组进行处理。要实现的PHP代码如下: <?phpfunction array_concat ($arr, $prefix) {foreach($arr as $key => $val) {if (isset($prefix[$key]) && is_string($val) && is_string($prefix[$key])) {$arr[

Go 数组赋值问题

package mainimport "fmt"type Student struct {Name stringAge int}func main() {data := make(map[string]*Student)list := []Student{{Name:"a",Age:1},{Name:"b",Age:2},{Name:"c",Age:3},}// 错误 都指向了最后一个v// a