求逆序对的三种方法

今天上线代提了句逆序数,并布置了一个编程求逆序数的思考题,正好借此机会总结一下求逆序对的三种方法:暴力、归并排序以及树状数组

暴力

第一种暴力:写两层暴力比较,时间复杂度O(n^2)

for (int i = 1; i < n; i++)
  for (int j = i + 1; j <= n; j++)
    if (a[i] > a[j]) cnt++;

但是这种解法虽然容易写,但这爆炸的时间复杂度显然是不够完美的,于是便有了接下来的两种方法

归并排序

众所周知,归并排序是将数列 a[l,h] 分成两半:a[l,mid] 和 a[mid+1,h] 分别进行归并排序,然后再将这两半合并起来。在合并的过程中(设 l<=i<=mid,mid+1<=j<=h),当 a[i]<=a[j] 时,并不产生逆序数;当a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并排序中的合并过程中计算逆序数。

这里用洛谷的一道题来作为例题:P1908 逆序对

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 5e5 + 1;
ll n, a[maxn], ans, b[maxn];

inline ll read() {
  ll x = 0, k = 1; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  return x * k;
}

inline void work(ll l, ll r) {
  if (r - l > 0) {
    ll mid = (l + r) / 2;
    ll num = l, num1 = l, num2 = mid + 1;
    work(l, mid);
    work(mid + 1, r);
    while (num1 <= mid || num2 <= r) {
      if (num2 > r || (num1 <= mid && a[num1] <= a[num2]))
  	b[num++] = a[num1++];
      else {
        b[num++] = a[num2++];
  	ans += mid + 1 - num1;
      }
    }
    for (int i = l; i <= r; i++) a[i] = b[i];
  }
}

int main() {
  n = read();
  for (int i = 1; i <= n; i++) a[i] = read();
  work(1, n);
  printf("%lld", ans);
  return 0;
}

树状数组

考虑根据值来建树状数组,初始树状数组全为0。现在按照序列从左到右将数据的值对应的位置的数加一,代表又有一个数出现。因此,在循环到第 i 项时,前 i-1 项已经加入到树状数组内了 , 树状数组内比 a[i] 大的都会与 a[i] 构成逆序对,因为它们一定出现的更早,所以产生的逆序对数量为i-query(a[i])

注:query(a[i])代表在树状数组内询问 1~a[i] 项的前缀和

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e7 + 10;
const int maxm = 1e4 + 10;

inline ll read() {
  ll x = 0, k = 1; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  return x * k;
}

struct node {
  int val, num;
}a[maxn];
ll n, ans, c[maxn], b[maxn];

inline int lowbite(int i) {
  return i & (-i);
}

inline void add(int i, int k) {
  while (i <= n) {
    c[i] += k;
    i += lowbite(i);
  }
}

inline int query(int i) {
  int sum = 0;
  while (i > 0) {
    sum += c[i];
    i -= lowbite(i);
  }
  return sum;
}

inline bool cmp(node x, node y) {
  if (x.val == y.val) return x.num < y.num;
  return x.val < y.val;
}

int main() {
  n = read();
  for (ll i = 1; i <= n; i++) {
    a[i].val = read();
    a[i].num = i;
  }
  sort(a + 1, a + n + 1, cmp);
  for (ll i = 1; i <= n; i++) b[a[i].num] = i;
  for (ll i = 1; i <= n; i++) {
    add(b[i], 1);
    ans += i - query(b[i]);
  }
  printf("%lld\n", ans);
  return 0;
}
posted @ 2021-03-01 23:16  Moominn  阅读(900)  评论(0编辑  收藏  举报