作者注:本文中代码均在 C++14 (GCC 9) O2 编译环境下编译通过。
Part 1 - 分治算法
例1 - 洛谷P1908 逆序对
Description
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj 且 i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
Input
第一行,一个数 n n n,表示序列中有 n n n 个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109。
Output
输出序列中逆序对的数目。
Sample Input
6
5 4 2 6 3 1
Sample Output
11
Accepted Code 1
归并排序 (merge sort) 是利用归并的思想实现的排序方法,该算法采用经典的“分而治之”的策略,主要分为递归拆分子序列和合并相邻有序子序列两个步骤。归并排序是一种稳定的排序算法,其时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
对于本题,如果我们想要将一个序列排成升序序列,那么每次拆分后再合并时,左右两个子序列都是升序的,因此只需要统计右侧的序列中的每个数分别会与左侧的序列产生多少逆序对。
另外需要注意的一点是,本题的结果可能会超出int
的范围,因此需要开long long
类型的全局变量用来存放结果。
Language: C++
#include <iostream>
using namespace std;
const int N = 500005;
long long ans;
int a[N];
int t[N];
void merge_sort(int b, int e) {
if (b == e) {
return;
}
int mid = b + ((e - b) >> 1);
int i = b;
int j = mid + 1;
int k = b;
merge_sort(b, mid);
merge_sort(mid + 1, e);
while (i <= mid && j <= e) {
if (a[i] <= a[j]) {
t[k++] = a[i++];
} else {
t[k++] = a[j++];
ans += mid - i + 1;
}
}
while (i <= mid) {
t[k++] = a[i++];
}
while (j <= e) {
t[k++] = a[j++];
}
for (int m = b; m <= e; m++) {
a[m] = t[m];
}
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
merge_sort(0, n - 1);
cout << ans << endl;
return 0;
}
Accepted Code 2
本题还可以用数据离散化+树状数组的解法解决。
根据值来建立树状数组,在循环到第 i i i 项时,前 i − 1 i-1 i−1 项都已加入树状数组。树状数组内比 a i a_i ai 大的元素都会与 a i a_i ai 构成逆序对,逆序对数量为 i − q u e r y ( a i ) i-query(a_i) i−query(ai),其中 q u e r y ( a i ) query(a_i) query(ai) 代表在树状数组内询问 1 1 1 到 a i a_i ai 项的前缀和。
观察数据范围,容易发现根据值来建立树状数组的空间不够,因此可以考虑对数据离散化。我们只需要数据之间的相对大小关系,因此可以将数据排序,再用区间 [ 1 , n ] [1,n] [1,n] 上的数表示原始数据的相对大小关系,最后对这个新的序列建立树状数组即可。
题目描述中最后一句说明序列中可能有重复数字,不处理相等的元素可能会导致求解过程的错误。当有与 a i a_i ai 相等的元素在 a i a_i ai 前被加入树状数组且其相对大小标记更大的时候,就会误将两个相等的数判定为逆序对。我们在排序的时候,需要将先出现的数字的标记也设置为较小的,可以考虑在输入数据时利用结构体数组存储,结构体成员变量分别为原始数据以及其对应的原始下标。在排序时可以指定sort
函数的cmp
参数,将原始数据作为第一关键字,原始下标作为第二关键字,对结构体数组升序排序。
Language: C++
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 500005;
int n;
struct node {
int idx;
int val;
} a[N];
int tree[N];
int rank_[N];
long long ans;
inline void insert(int p, int d) {
for (; p <= n; p += p & -p) {
tree[p] += d;
}
}
inline int query(int p) {
int ret = 0;
for (; p; p -= p & -p) {
ret += tree[p];
}
return ret;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
a[i].idx = i;
}
sort(a + 1, a + n + 1, [](const node& o1, const node& o2) {
return o1.val == o2.val ? o1.idx < o2.idx : o1.val < o2.val;
});
for (int i = 1; i <= n; i++) {
rank_[a[i].idx] = i;
}
for (int i = 1; i <= n; i++) {
insert(rank_[i], 1);
ans += i - query(rank_[i]);
}
cout << ans << endl;
return 0;
}
Part 2 - 贪心算法
例2.1 - Codeforces 1339C Powered Addition
Description
You have an array a a a of length n n n. For every positive integer x x x you are going to perform the following operation during the x x x-th second:
- Select some distinct indices i 1 , i 2 , ⋯ , i k i_1,i_2,\cdots,i_k i1,i2,⋯,ik which are between 1 1