This blog's target audience is for newbies who don't know this (like me!). So, if you knew this before, it would be great if you read this blog and give me some valuable feedbacks. Thanks ^^!
Now for the intro:
A while ago, I met a problem where it requires me to count the number of inversion of an array. Having 2 braincells, I knew nothing about this. So, I decided to ask my friends ballss and quanhoangbla. Of course, they are good friends so they taught me everything about that. NO. ballss just called me dumb, so I decided to learn it myself. That's when I stumbled across this blog. This blog, in my opinion, is a great blog, but it was not really well written. As a result, it got heavily downvoted (I recommend you upvote that blog, although it's most likely that the author have stopped doing CP, since the last time he/she visited was 9 months ago). Although I understand the main idea, I think that it might be confusing for other newbies too. So, I decided to rewrite that blog, to understand about this topic more and to contribute to the community :D.
Also, as you can notice from the title, this is the first part of this series. If I'm not lazy, I may write some more parts. If you want me to rewrite a blog, you can DM me for that topic.
NOTE THAT ALL OF THE IDEAS IN THIS BLOG WERE FROM THIS BLOG. WHAT I DO IS JUST REWRITE THAT BLOG SO THAT IT'S EASIER TO UNDERSTAND
Now, for the main part of the blog:
Counting inversion using mergesort/fenwick tree
Problem statement:
You were given an array $$$a$$$ of size $$$n$$$ $$$(1 ≤ n ≤ 10^5,$$$ $$$a_i ≤ 10^5$$$ $$$\forall$$$ $$$1 ≤ i ≤ n)$$$. Count the number of inversion in this array.
What's an inversion count?Inversion count is the number of index $$$i$$$ and $$$j$$$ that sastifies:
$$$i \lt j$$$
$$$a_i \gt a_j$$$
For example, you were given this array: $$$1, 5, 2, 4, 3$$$. This array has 4 inversions, with index: $$$(2, 3), (2, 4), (2, 5), (4, 5)$$$. $$$(2, 3)$$$ is an inversion, since $$$2 \lt 3$$$ and $$$a_2 = 5 \lt a_3 = 2$$$
Naive approach: Brute force
Since the constraint of $$$n$$$ is up to $$$10^5$$$, so this approach will certainly get TLE. Tho, I think it's good to have this part so that you can understand the correct solution better.
The main idea of this method is to use 2 for loops to iterate through all of the possible pair of index. Then, you check if they sastify the condition. If yes, increase the count by 1.
Code:#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 69;
int a[N]; //the given array
long long cnt; //The number of inversions. Use long long to avoid overflow
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) {
for (int j = i + 1; j <= n; j ++) {
//by writing the for loop like this, the first condition (i < j) is already satisfied. Now, we
//just need to check the second condtion (a_i > a_j)
if (a[i] > a[j]) {
cnt ++;
}
}
}
cout << cnt;
}
Time complexity: $$$O(n^2)$$$
Space complexity: $$$O(n)$$$
Solution 1: Using Mergesort
It is recommended to read this blog to know more about merge sort
If you are lazy reading the blog above, what is merge sort?Merge sort is a sorting algorithm. How it works is simple:
Let split array $$$a$$$ into 2 halves: array $$$b$$$ and $$$c$$$. Assume that you can sort the array $$$b$$$ and $$$c$$$. Then, to sort array $$$a$$$, you just simply need to merge array $$$b$$$ with $$$c$$$ using two pointers. Now, the array $$$a$$$ is sorted. To sort array $$$b$$$ and $$$c$$$, do the same thing again: split the array $$$b$$$ into $$$b1$$$ and $$$b2$$$, sort them, then merge them. Do this until the base case, where the length of the array is 1, you get the merge sort algorithm.
Time complexity: $$$O(n$$$ $$$log$$$ $$$n)$$$
For the main idea of this method:
You need to count the number of inversions in the array $$$a$$$. You split them into 2 halves: array $$$b$$$ and $$$c$$$ (array $$$b$$$ contains the first half of array $$$a$$$, and array $$$c$$$ contains the second half). Assume that you have the inversion count for array $$$b$$$ and $$$c$$$. So, to get the inversion count of array $$$a$$$, you need to add the number of inversions in array $$$b$$$, array $$$c$$$, and the cross inversions (the number of index $$$i$$$ and $$$j$$$ so that, $$$i$$$ is in array $$$b$$$, $$$j$$$ is in array $$$c$$$ and $$$a_i \gt a_j$$$. This is an inversion since $$$i \lt j$$$). Think how to count this.
Hint:Maybe count this when you merge the 2 arrays.
Remember when you merge 2 sorted arrays using two pointers? To do this, you iterate pointer $$$i$$$ through the array $$$b$$$, pointer $$$j$$$ through the array $$$c$$$, and add the smaller one to the answer array. So, what if $$$b_i \gt c_j$$$? (In other word, what do you do when $$$a_i \gt a_j$$$?)
Solution:Read the hint. If $$$b_i \gt c_j$$$, you increase the cross inversions count by one. So, now, you get the inversion count of array $$$a$$$ by take the sum of the inversion count in array $$$b$$$, $$$c$$$, and the cross inversions. To find the number of inversions in array $$$b$$$, you do the same thing: split the array $$$b$$$ into two halves $$$b1$$$ and $$$b2$$$, then count the inversion of array $$$b1$$$ and $$$b2$$$ using the same method, as well as the cross inversions of $$$b1$$$ and $$$b2$$$. Problem solved!
Code: (Maybe it's going to be a little bit hard to understand, I tried my best to make it easy to comprehend, sorry for any inconvenience)#include <bits/stdc++.h>
using namespace std;
//this code is made by a human, not AI. The comments and long function name is to make the code easier to understand
//sometimes I regret making long function name -_-
const int N = 1e5 + 69;
vector <long long> a; //the given array
int n;
long long count_cross_inversion(vector <long long> &a, int l, int mid, int r) {
vector <long long> b, c; //array b and c
b.push_back(0);
c.push_back(0); //push_back to make them 1-index
for (int i = l; i <= mid; i ++) {
b.push_back(a[i]);
}
for (int i = mid + 1; i <= r; i ++) {
c.push_back(a[i]);
}
//making array b and c
int l1 = mid - l + 1;
int l2 = r - mid; //the length of array b and c
int i = 1;
int j = 1; //the two pointers
int idx = l; //the current index of array a
long long ans = 0;
while (i <= l1 && j <= l2) {
if (b[i] <= c[j]) { //normal case
a[idx] = b[i];
i ++;
} else if (b[i] > c[j]) {
//this is the case where b[i] > c[j] and i < j; aka the case where there's an inversion
a[idx] = c[j];
ans += (l1 - i + 1); //we add l1 - i + 1, because if b[i] > c[j] then b[i + 1] > c[j], ..., b[l1] > c[j] (since the array b is sorted)
j ++;
}
idx ++;
}
//add remaining numbers, as usual
while (i <= l1) {
a[idx] = b[i];
idx ++;
i ++;
}
while (j <= l2) {
a[idx] = c[j];
idx ++;
j ++;
}
return ans;
}
long long count_inversion(vector <long long> &a, int l, int r) { //count the number of inversions of array a in the range l and r
if (l == r) { //base case, where the length of a is 1. The answer for this case is 0
return 0;
}
int mid = (l + r) / 2; //mid is the midpoint of the array, to split the array a into array b and c.
long long res = 0;
res += count_inversion(a, l, mid); //count the number of inversions in range l and mid (aka in array b, as well as sort the array b)
res += count_inversion(a, mid + 1, r); //same thing for array c
res += count_cross_inversion(a, l, mid, r);
return res;
}
int main() {
cin >> n;
a.push_back(0); //push back to make the vector 1-index
for (int i = 1; i <= n; i ++) {
int x; cin >> x;
a.push_back(x);
}
cout << count_inversion(a, 1, n);
}
Time complexity: $$$O(n$$$ $$$log$$$ $$$n)$$$
Space complexity: $$$O(n)$$$
Solution 2: Using Fenwick tree
It is recommended to read this blog to understand what is Fenwick tree. Explaining this is quite long so, for the sake of simplicity, assume that you know how fenwick tree works (by reading the blog I recommended) ^^
The main idea of this algorithm is, you build a frequency array, let's call it $$$freq$$$. Then, $$$freq_i$$$ will be the number of times that $$$i$$$ appeared. So, the number of indexes that are less than are equal to $$$i$$$ would be
$$$freq_1 + freq_2 + ... + freq_i = prefix_i$$$Where $$$prefix_i$$$ is the prefix sum of the first $$$i$$$ elements in array $$$freq$$$. You can get this value in $$$log$$$ $$$n$$$ time using fenwick tree.
So, to count the number of inversions in an array $$$a$$$, initially, we set the array $$$freq$$$ to $$$0$$$. Then, you would need to iterate $$$x$$$ in array $$$a$$$ from back to front. After that, you add the numbers of indexes that are less than $$$a_x$$$ to the answer, which is the $$$prefix_{a_x - 1}$$$ to the array. This can be done in $$$log$$$ $$$n$$$ time. Note that you add $$$prefix_{a_x - 1}$$$, not $$$prefix_{a_x}$$$ since you need to count the number of elements that are less than $$$a_x$$$, not less than or equal to $$$a_x$$$.
Finally, you update the $$$freq_{a_x}$$$ by one. This mean that, you need to update the $$$prefix$$$ array as well. This can also be done in $$$log$$$ $$$n$$$ time using fenwick tree.
Code:#include <bits/stdc++.h>
using namespace std;
//this code is made by a human, not AI. The comments and long function name is to make the code easier to understand
//sometimes I regret making long function name -_-
const int N = 1e6 + 69;
long long fenwick_tree[N];
int n;
long long get(int i) {
int idx = i;
long long ans = 0;
while (idx) {
ans += fenwick_tree[idx];
idx -= idx & (-idx);
}
return ans;
}
void update(int u) { //increase the element of freq[u] by 1
int idx = u;
while (idx <= N) {
fenwick_tree[idx] ++;
idx += idx & (-idx);
}
}
//the whole thing above is the template for fenwick tree
int main() {
cin >> n;
vector <long long> a;
a.push_back(0); //push back to make the vector 1-index
for (int i = 1; i <= n; i ++) {
int x; cin >> x;
a.push_back(x);
}
long long count_inversion = 0;
for (int i = n; i >= 1; i--) { //iterate i from back to front
count_inversion += get(a[i] - 1); //add the answer by prefix[a[i] - 1]
update(a[i]); //update the fenwick tree
}
cout << count_inversion;
}
Time complexity: $$$O(n$$$ $$$log$$$ $$$n)$$$
Space complexity: $$$O(n)$$$
Bonus (this isn't in the original blog):
As you can see, the fenwick tree can only store elements that are less than are equal to $$$10^7$$$ (since that's the memory limit for an array in C++, I don't know for other languages). So, if the problem gives you $$$a_i$$$ up to $$$10^{18}$$$, what would you do? Of course, you can still use merge sort, but fenwick tree is way easier to implement.
Solution + code:Use integer compression!
If you didn't know, you can compress integers into smaller ones if the problem only require the less than, equal or larger than in value. The inversion count is in this type too!
For example, you were given an array $$$a = {123, 783274, 1037, 19472, 21}$$$. The inversion count of this array is 6. You can compress this array into $$${2, 5, 3, 4, 1}$$$ without changing the inversion count.
Code:#include <bits/stdc++.h>
using namespace std;
//this code is made by a human, not AI. The comments and long function name is to make the code easier to understand
//sometimes I regret making long function name -_-
const int N = 1e6 + 69;
long long fenwick_tree[N];
map <long long, int> m;
int n;
long long get(int i) {
int idx = i;
long long ans = 0;
while (idx) {
ans += fenwick_tree[idx];
idx -= idx & (-idx);
}
return ans;
}
void update(int u) { //increase the element of freq[u] by 1
int idx = u;
while (idx <= N) {
fenwick_tree[idx] ++;
idx += idx & (-idx);
}
}
//the whole thing above is the template for fenwick tree
int main() {
cin >> n;
vector <long long> a;
set <long long> s; //add to a set to sort the numbers, as well as only store elements that aren't the same
a.push_back(0); //push back to make the vector 1-index
for (int i = 1; i <= n; i ++) {
int x; cin >> x;
a.push_back(x);
s.insert(x);
}
int idx = 1; // the current number
for (long long i : s) {
m[i] = idx; //use map to remember what is the compressed number of an elements
idx ++;
}
for (int i = 1; i <= n; i ++) {
a[i] = m[a[i]]; //compress the numbers
}
//the rest is exactly the same
long long count_inversion = 0;
for (int i = n; i >= 1; i--) { //iterate i from back to front
count_inversion += get(a[i] - 1); //add the answer by prefix[a[i] - 1]
update(a[i]); //update the fenwick tree
}
cout << count_inversion;
}
So, that's the end of the blog! This is my first time posting something useful (as the others are mostly shitposts -_-), so I hope that you like this. Also, if you want me to rewrite any blog, message me. (Of course, please send me a blog that I can understand, not a FFT blog...).
Thank you for reading, and have a nice day ;)
Why does the naive approach have to TLE?
(lol sorry we just did this today so its fresh in my mind. and yes it appears intended given that TL was set to 15 sec...)
link dont works
Order statistic tree works too
tf is that :skull:
also known as ordered set, can import it from pbds library