求逆序对数的NLogN解法:归并排序、树状数组和线段树
定义
对于一个包含N个非负整数的数组A[1..n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对。例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
朴素的枚举
双重循环搞定,O(N^2)的复杂度。程序就不写了。
用归并排序思想解决
网上思路很多,理解归并排序就不难解决了。具体的归并排序就不介绍了,这里给出RQNOJ 173的代码
var a,b:array[1..1000000] of longint; i,j,k,n:longint; ans:int64; procedure mergesort(l,r:longint); var i,j,k,m:longint; begin if l>=r then exit; m:=(l+r) shr 1; mergesort(l,m); mergesort(m+1,r); i:=l; j:=m+1; k:=l; repeat if a[i]>a[j] then begin b[k]:=a[j]; inc(ans,m-i+1);//关键在于这步 inc(j); inc(k); end else begin b[k]:=a[i]; inc(k); inc(i); end until (i>m) or (j>r); while i<=m do begin b[k]:=a[i]; inc(k); inc(i); end; while j<=r do begin b[k]:=a[j]; inc(k); inc(j); end; for i:=l to r do a[i]:=b[i]; end; begin readln(n); for i:=1 to n do read(a[i]); mergesort(1,n); writeln(ans); end.
用支持区间修改的数据结构解决
用线段树和树状数组可以在NLogN的时间内求逆序对数。
一对数是逆序对当且仅当i < j && a[i] >a[j]。
过程
我们将序列的元素逆序插入,插入a[i]就是update(a[i],1)。每次插入前ans += getsum(1,a[i]-1)。最后ans就是逆序对数了。
正确性
因为是逆序插入的,所以在getsum中的元素的下标都比i大,而且查询a[i]-1,满足值比a[i]小的条件。
这两个操作都是线段树和树状数组的典型应用,写起来应该比不太常用的归并排序熟练地多。而且树状数组代码能写得很短。
下面是HDU 1394的代码:
/* 题意:一个由0..n-1组成的序列,每次可以把队首的元素移到队尾,求形成的n个序列最小逆序对数目 算法: 由树状数组求逆序对。加入元素i即把以元素i为下标的a[i]值+1,从队尾到队首入队, 每次入队时逆序对数 += getsum(i - 1),即下标比它大的但是值比它小的元素个数。 因为树状数组不能处理下标为0的元素,每个元素进入时+1,相应的其他程序也要相应调整。 求出原始的序列的逆序对个数后每次把最前面的元素移到队尾,逆序对数即为 原逆序对数+比i大的元素个数-比i小的元素个数,因为是0..n,容易直接算出,每次更新min即可。 体会:不要照搬别人的程序,细节处理不一样会Debug很久的 */ #include <stdio.h> #define MAXN 10000 int c[MAXN],a[MAXN],n; int min(int a,int b) { if (a < b) return a; else return b; } int lowbit(int i) { return i & -i; } void update(int i,int x) { while (i <= n) { c[i] += x; i += lowbit(i); } } int getsum(int x) { int sum = 0; while (x > 0) { sum += c[x]; x -= lowbit(x); } return sum; } int main() { while (scanf("%d", &n) == 1) { for (int i = 1; i <= n; i++) c[i] = 0; int sum = 0; for (int i = 1; i <= n; i ++) { scanf ("%d", &a[i]); } for (int i = n; i >= 1; i --) { update (a[i] + 1, 1); sum += getsum (a[i]); } int ans = sum; for (int i = 1; i <= n; i++) { sum = sum - a[i] + (n - a[i] - 1); ans = min(ans, sum); } printf("%d\n", ans); } return 0; }
线段树的代码:
/* 题意:一个由0..n-1组成的序列,每次可以把队首的元素移到队尾, 求形成的n个序列最小逆序对数目 算法:将元素依次插入线段树,每次增加的逆序对数为比它大的已经插入的 数的个数,可以用线段树维护,由于元素值为0..n,每次移动可求出增减 逆序对的数量更新。 体会:线段树敲了20分钟,如果是PASCAL并且写得难看些10分钟应该能搞定。 Debug 10分钟,主要是更新时没考虑清楚+记错题目求成最大逆序对数了 */ #include <stdio.h> #define MAXN 100000 #define ROOT 1 struct node { int left,right,sum; }t[MAXN]; int val[MAXN]; int n; void build(int p, int left, int right) { int m; t[p].left = left; t[p].right = right; t[p].sum = 0; if (left == right) return; m = (left + right) / 2; build(p*2, left, m); build(p*2+1, m+1, right); } void update(int p, int goal, int add) { t[p].sum += add; if (t[p].left == t[p].right) return; int m = (t[p].left + t[p].right) / 2; if (goal <= m) update(p*2, goal, add); if (goal > m) update(p*2+1, goal, add); } int getsum(int p, int left, int right) { if (left > right) return 0; if (t[p].left == left && t[p].right == right) return t[p].sum; int m = (t[p].left + t[p].right) / 2; if (right <= m) return getsum(p*2, left, right); else if (left > m) return getsum(p*2+1, left, right); else return getsum(p*2, left, m) + getsum(p*2+1, m + 1, right); } int main() { while (scanf("%d", &n) == 1) { build(ROOT, 0, n - 1); int i,sum = 0,ans; for (i = 1; i <= n; i++) { scanf("%d", &val[i]); sum += getsum(ROOT, val[i], n - 1); update(ROOT, val[i], 1); } ans = sum; for (i = 1; i <= n; i++) { sum = sum + (n - val[i] - 1) - val[i]; ans = sum < ans ? sum : ans; } printf("%d\n", ans); } return 0; }
POJ 3067
/* 题意:左边一排城市,右边一排城市,中间若干条道路,求交点。 算法:转化为求逆序对的问题。两条路有交点当且仅当A.x<B.x && A.y>B.y。将路径以x为第一关键字 y为第二关键字升序排序后就是求y的逆序对了。树状数组可以解决。 此外还有一个复杂度为mn的方法,详见http://www.cnblogs.com/penseur/archive/2011/04/14/2015998.html 注意点: 1.题目的k很大。数组要开得大。 2.由于树状数组不能处理下标为0的元素,所以输入的时候y下标都加上1. 收获与感想: 1.复习了用区间和求逆序对的方法 2.学会了调用qsort函数 3.进一步理解了指针 一点废话: 这题做了一天啊!!!整整一天啊!!!网上的资料不可信啊,给一段编译出错的代码啊!!!关键时刻还是要看K&R 啊!!!MAXM和MAXN搞错了又是调试很久啊~~~ 2011-07-18 13:07 */ #include <stdio.h> #include <stdlib.h> #define MAXN 1100 #define MAXM 1000100 int n, m, k; struct point { int x, y; }a[MAXM]; long long c[MAXN]; int lowbit (int i) { return i & -i; } long long getsum(int i) { long long sum = 0; while (i > 0) { sum += c[i]; i -= lowbit(i); } return sum; } void add(int i) { while (i <= MAXN) { c[i]++; i += lowbit(i); } } int cmp(const void *a,const void *b) { struct point *c = (struct point *)a; struct point *d = (struct point *)b; if (c->x == d->x) return d->y - c->y; return d->x - c->x; } int main() { int t, j, i; long long ans; scanf("%d", &t); for (j = 1; j <= t; j++) { ans = 0; scanf("%d%d%d", &n, &m, &k); memset(c, 0, sizeof(c)); for (i = 1; i <= k; i++) scanf("%d%d", &a[i].x, &a[i].y); for (i = 1; i <= k; i++) a[i].x++; qsort(a+1, k, sizeof(a[0]), cmp); // for (i = 1; i <= k; i++) // printf("%d %d\n", a[i].x , a[i].y); // printf("\n"); for (i = 1; i <= k; i++) { ans += getsum(a[i].y-1); add(a[i].y); } printf("Test case %d: %lld\n",j, ans); } return 0; }