本文主要是介绍牛客——火柴排队(树状数组与归并排、逆序对),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
链接:登录—专业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;
}
这篇关于牛客——火柴排队(树状数组与归并排、逆序对)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!