题目-求逆序对

在这里插入图片描述

归并排序(分治)求逆序对

在这里插入图片描述

在归并排序向上合并的过程中, 对于两段有序的序列合并为一个序列, 如果是顺序的左边的所有数字都应该 ≤ \le 右边

在这里插入图片描述

因此当出现 w [ p 1 ] > w [ p 2 ] w[p1] > w[p2] w[p1]>w[p2], [ p 1 , m i d ] [p1, mid] [p1,mid] p 2 p2 p2构成了逆序对, 答案需要累加
m i d − p 1 + 1 mid - p1 + 1 midp1+1

重要误区

为什么当出现 w [ p 1 ] > w [ p 2 ] w[p1] > w[p2] w[p1]>w[p2], 不能用右半边统计, 也就是 j − ( m i d + 1 ) + 1 j - (mid + 1) + 1 j(mid+1)+1

关键在于统计重复, 具体的来说, 如果前面出现了 w [ i ] > w [ p 2 ] w[i] > w[p2] w[i]>w[p2], 按照上面的方式逆序对的数量将会累计 p 2 − ( m i d + 1 ) + 1 p2 - (mid + 1) + 1 p2(mid+1)+1, 但是当出现了当前情况 w [ i ] > w [ j ] w[i] > w[j] w[i]>w[j]的时候 p 2 − ( m i d + 1 ) + 1 p2 - (mid + 1) + 1 p2(mid+1)+1这一段将会被统计两次!, 因此是错误的

为什么统计左侧就是正确的?
当出现 w [ p 1 ] > w [ p 2 ] w[p1] > w[p2] w[p1]>w[p2]的时刻, 立刻统计出 m i d − p 1 + 1 mid - p1 + 1 midp1+1这一段都是 w [ p 2 ] w[p2] w[p2]构成逆序对, 并且统计完之后直接将 w [ p 2 ] w[p2] w[p2]加入到答案序列中, 不存在重复统计的情况!

算法步骤

  • 对序列进行递归划分
  • 向上合并的过程中统计逆序对的数量

算法时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

LL ans;
int n, w[N], tmp[N];

void merge_sort(int l, int r) {
   
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);

    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
   
        if (w[i] <= w[j]) tmp[k++] = w[i++];
        else {
   
            ans += mid - i + 1;
            tmp[k++] = w[j++];
        }
    }

    while (i <= mid) tmp[k++] = w[i++];
    while (j <= r) tmp[k++] = w[j++];

    for (int i = 0; i < k; ++i) w[i + l] = tmp[i];
}

int main() {
   
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 0; i < n; ++i) cin >> w[i];

    merge_sort(0, n - 1);

    cout << ans << '\n';

    return 0;
}

树状数组求逆序对

如果一段序列是有序的, 那么对于 i < j i < j i<j, 有 w i ≤ w j w_i \le w_j wiwj, 因此对于当前位置 i i i, 可以统计前面有多少个数字 > w i > w_i >wi w i w_i wi构成逆序对, 然后再将 w i w_i wi加入集合中, 统计前缀和可以使用树状数组实现

算法步骤

  • 因为数据范围很大, 需要对原数据进行离散化
  • 实现树状数组查询和修改
  • 查询对于当前位置 i i i, 1 ∼ i − 1 1 \sim i - 1 1i1有多少个数字比当前数字大
  • 将当前数字加入到集合当中

注意: 树状数组的下标从 1 1 1开始, 离散化的时候需要 + 1 + 1 +1

代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

int n, w[N];
vector<int> nums;
int tr[N];

int lowbit(int x) {
   
    return x & -x;
}

void modify(int u, int val) {
   
    for (int i = u; i <= n; i += lowbit(i)) tr[i] += val;
}

LL get(int u) {
   
    LL ans = 0;
    for (int i = u; i; i -= lowbit(i)) ans += tr[i];
    return ans;
}


// 因为树状数组下标是从1开始, 因此需要 + 1, 否则会TLE
int find(int x) {
   
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin() + 1;
}

int main() {
   
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 0; i < n; ++i) {
   
        cin >> w[i];
        nums.push_back(w[i]);
    }

    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    LL ans = 0;
    for (int i = 0; i < n; ++i) {
   
        int x = find(w[i]);
        ans += get(n) - get(x);
        modify(x, 1);
    }

    cout << ans << '\n';

    return 0;
}

离散化作为树状数组边界的代码

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

int n, w[N], m;
vector<int> nums;
int tr[N];

int lowbit(int x) {
   
    return x & -x;
}

void modify(int u, int val) {
   
    for (int i = u; i <= m; i += lowbit(i)) tr[i] += val;
}

LL get(int u) {
   
    LL ans = 0;
    for (int i = u; i; i -= lowbit(i)) ans += tr[i];
    return ans;
}

// 因为树状数组下标是从1开始, 因此需要 + 1, 否则会TLE
int find(int x) {
   
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin() + 1;
}

int main() {
   
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 0; i < n; ++i) {
   
        cin >> w[i];
        nums.push_back(w[i]);
    }

    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    m = nums.size();

    LL ans = 0;
    for (int i = 0; i < n; ++i) {
   
        int x = find(w[i]);
        // 注意在这里是get(m)
        ans += get(m) - get(x);
        modify(x, 1);
    }

    cout << ans << '\n';

    return 0;
}