链接:登录—专业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 取模的结果。
一、归并排序的基本思想是将待排序的序列分成两个子序列,分别对两个子序列进行排序,然后将两个已排序的子序列合并成一个有序序列。通过递归地将序列分解成足够小的子序列,再将子序列两两合并,最终得到完全有序的序列。
归并排序的具体步骤如下:
-
分解(Divide):将待排序的序列不断二分,直到得到足够小的子序列。
-
解决(Conquer):对每个子序列进行排序,可以使用递归调用归并排序。
-
合并(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 开始,对应于原始数组的元素位置。数组的每个元素存储了一部分原始数组的元素和,具体的计算方式是通过二进制的技巧来实现的。
树状数组的基本操作包括单点更新和前缀和查询:
-
单点更新:通过不断修改 BIT 数组的某些元素,实现对原始数组中某个位置上元素的更新。更新操作的时间复杂度为 O(logn)。
-
前缀和查询:通过不断累加 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;
}