从CF1676H2看逆序对的两种求法
思路
原问题可以以直接转化成求 \(a_i >= a_j(i <j)\) 的数量。
归并排序
原理很直接,归并排序就是为了减少逆序对的数量,所以直接在操作的时候记录减少的数量即可
排序后的数组无逆序对,归并排序的合并操作中,每次后段首元素被作为当前最小值取出时,前段剩余元素个数之和即是合并操作减少的逆序对数量
但是实现比较麻烦
std::vector<int> a, temp;
int n;
i64 merge(int left, int mid, int right) {
int i(left), j(mid), k(left);
i64 count(0);
while(i <= mid - 1 and j <= right) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
}
else {//因为题目要求a[i] >= a[j]
temp[k++] = a[j++];
count += mid - i;//又因为合并操作已保证左区间有序,所以i之后到mid的a[i]都大于等于a[j]
}
}
while(i <= mid - 1)
temp[k++] = a[i++];
while(j <= right)
temp[k++] = a[j++];
for (i = left; i <= right; i++)
a[i] = temp[i];
return count;
}
i64 mergeSort(int left, int right) {
int mid(0);
i64 count(0);
if (right > left) {
mid = left + right >> 1;
count = mergeSort(left, mid);
count += mergeSort(mid + 1, right);
count += merge(left, mid + 1, right);
}
return count;
}
i64 aInversion(int n) {
temp.resize(n);
return mergeSort(0, n - 1);
}
void solve() {
std::cin >> n;
a.resize(n);
for (auto& x : a) std::cin >> x;
std::cout << aInversion(n) << '\n';
}
树状数组or线段树
题目说了\(a_i \leq n\)
考虑用 \(a_i\) 来作为一段 \(01\) 前缀和中 \(1\) 的下标,即通过前缀和来计数
从后往前扫,每次先查询包括该下标到 \(1\) (因为要求 \(a_i >= a_j\))的前缀和,显然就是对于当前数字的逆序对个数
- 树状数组
template <typename T>
struct Fenwick {
int n;
std::vector<T> a;
Fenwick(int n_ = 0) {
init(n_);
}
void init(int n_) {
n = n_;
a.assign(n, T{});
}
void add(int x, const T &v) {
for (int i = x + 1; i <= n; i += i & -i) {
a[i - 1] = a[i - 1] + v;
}
}
T sum(int x) {
T ans{};
for (int i = x; i > 0; i -= i & -i) {
ans = ans + a[i - 1];
}
return ans;
}
};
void solve() {
int n;
std::cin >> n;
Fenwick<int> fen(n);
std::vector<int> a(n);
for (auto& x : a) std::cin >> x, x--;
i64 ans(0);
for (int i = n - 1; i >= 0; i--) {
ans += fen.sum(a[i] + 1);//这里是因为前面对数据做了--防越界处理,而该fenwick的实现中查询是右开的
fen.add(a[i], 1);
}
std::cout << ans << '\n';
}
- 线段树
template<class Info>
struct SegmentTree {
int n;
std::vector<Info> info;
SegmentTree() : n(0) {}
SegmentTree(int n_, Info v_ = Info()) {
init(n_, v_);
}
template<class T>
SegmentTree(std::vector<T> init_) {
init(init_);
}
void init(int n_, Info v_ = Info()) {
init(std::vector(n_, v_));
}
template<class T>
void init(std::vector<T> init_) {
n = init_.size();
info.assign(4 << std::__lg(n), Info());
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
if (r - l == 1) {
info[p] = init_[l];
return;
}
int m = (l + r) / 2;
build(2 * p, l, m);
build(2 * p + 1, m, r);
pull(p);
};
build(1, 0, n);
}
void pull(int p) {
info[p] = info[2 * p] + info[2 * p + 1];
}
void modify(int p, int l, int r, int x, const Info &v) {
if (r - l == 1) {
info[p] = info[p] + v;
return;
}
int m = (l + r) / 2;
if (x < m) {
modify(2 * p, l, m, x, v);
} else {
modify(2 * p + 1, m, r, x, v);
}
pull(p);
}
void modify(int p, const Info &v) {
modify(1, 0, n, p, v);
}
Info rangeQuery(int p, int l, int r, int x, int y) {
if (l >= y || r <= x) {
return Info();
}
if (l >= x && r <= y) {
return info[p];
}
int m = (l + r) / 2;
return rangeQuery(2 * p, l, m, x, y) + rangeQuery(2 * p + 1, m, r, x, y);
}
Info rangeQuery(int l, int r) {
return rangeQuery(1, 0, n, l, r);
}
};
struct Info {
i64 sum;
};
Info operator+(Info a, Info b) {
a.sum += b.sum;
return a;
}
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto& x : a) std::cin >> x, x--;
i64 ans(0);
SegmentTree<Info> T(n + 1);
for (int i = n - 1; i >= 0; i--) {
ans += T.rangeQuery(0, a[i] + 1).sum;//这里是因为前面对数据做了--防越界处理,而该segmentTree的实现中查询是左闭右开的
T.modify(a[i], {1});
}
std::cout << ans << '\n';
}