树状数组求逆序对
我们知道,求逆序对最典型的方法就是归并排序,但是还有一种方法就是树状数组。假如你理解了树状数组,树状数组求逆序对相比归并排序排序要更好理解一些,而且树状数组的代码量也要少一些。
我们先看一下逆序对是什么吧。
逆序对就是序列a中ai>aj且i<j的有序对。
根据上面的定义我们很快的就可以写出O(n^2)的算法,即枚举j,再枚举所有小于j的i,统计ai>aj的数量。但这个算法当时间复杂度过高。
如果我们能快速的统计出ai>aj的数量,时间复杂度就可以得到很好的提升。
树状数组就可以很好的做到这一点。
我们可以先开一个大小为a的最大值的数组t,每当读入一个数时,我们可以用桶排序的思想,将t[a[i]]加上1,然后我们统计t[1]~t[a[i]]的和ans,ans - 1(除掉这个数本身)就是在这个数前面有多少个数比它小。我们只要用i-ans就可以得出前面有多少数比它大,也就是逆序对的数量。
#include <bits/stdc++.h> #define lowbit(x) (x)&(-x) using namespace std; const int maxn = 1e6+10; int t[maxn],n,result; void add(int x) { while(x<=maxn) { t[x]++; x += lowbit(x); } } int query(int x) { int ans=0; for (;x;x-=lowbit(x)) ans+=t[x]; return ans; } int main() { int temp; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&temp); add(temp); result += i - query(temp); } printf("%d\n",result); return 0; }
现在这个代码可以在数的最大值比较小的时候可以正确的得出答案,如果数据很大,这回造成我们要开的空间很大。
我们是否可以适当的减少空间的需求呢?我们看看下面这些数:
1 2 3 4 5 10
这6个数我们需要使用大小10的数组来存储,我们仔细想想,可以发现中间 6 7 8 9 这4个位置是没有用到的,也就是说这4个空间被浪费了。怎样减少这样的浪费呢?
我们可以在读完数数据后对他进行从小到大排序,我们用排完序的数组的下标来进行运算。这样可以保证小的数依旧小,大的数依旧大。这一步叫做离散化。
struct Node { int v;//数字本身 int order;//原序列的的下标 }a[500005];
int reflect[500005];//用来存储原数第i个数的order下标是什么
//计算relect数组
relect[a[i].order] = i;
以上就是离散化的核心部分代码。
完整程序
代码来自:http://blog.csdn.net/SeasonJoe/article/details/50193789?locationNum=15&fps=1
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; const int maxn= 500005; int aa[maxn];//离散化后的数组 int c[maxn]; //树状数组 int n; struct Node { int v; int order; }a[maxn]; bool cmp(Node a, Node b) { return a.v < b.v; } int lowbit(int k) { return k&(-k); //基本的lowbit函数 } void update(int t, int value) { //即一开始都为0,一个个往上加(+1), int i; for (i = t; i <= n; i += lowbit(i)) c[i] += value; } int getsum(int t) { //即就是求和函数,求前面和多少就是小于它的个数 int i, sum = 0; for (i = t; i >= 1; i -= lowbit(i)) sum += c[i]; return sum; } int main() { int i; while (scanf("%d", &n), n) { for (i = 1; i <= n; i++) //离散化 { scanf("%d", &a[i].v); a[i].order = i; } sort(a + 1, a + n + 1,cmp);//从1到n排序,cmp容易忘 memset(c, 0, sizeof(c)); for (i = 1; i <= n; i++) aa[a[i].order] = i; __int64 ans = 0; for (i = 1; i <= n; i++) { update(aa[i], 1); ans += i - getsum(aa[i]); //减去小于的数即为大于的数即为逆序数 } printf("%I64d\n", ans); } return 0; }