方法一:归并排序
参考博客:http://www.mamicode.com/info-detail-556673.html 感谢!
问题分析:
1.对于n个小朋友中的任何一个小朋友k,分析可以得到:
a.排在k前面比k高的小朋友都会交换到k后面去(这种小朋友个数记为higher),
b.排在k后面比k低的小朋友都会交换到k前面去(这种小朋友个数记为lower),
所以最后排好序的时候,k至少交换higher+lower次。
2.先假设n个小朋友身高均不相等,我们通过归并排序可以求出每个小朋友的higher值,设小朋友k在第k位上,是第w矮的,则low = w-1 - (k-1-higher)。(w-1表示比k矮的总人数,k-1-higher表示排在k前面比k矮的人数)
3.归并排序求小朋友k的high值,初始时high[k]=0,在对身高数组a[]中a[l]~a[mid]和a[mid+1]~a[r]合并操作的时候,若对a[l]~a[mid]中的一个数a[j],若mid+1<=k<=r && a[k]<a[j],则higher[k] += mid-j+1(因为两个序列都是排好序的,所以a[j]~a[mid]都比a[k]大且排在a[k]前边,所以累加)
4.现在处理n个小朋友中,有身高相等的情况,转化为身高全不相等即可:
a.对小朋友的身高按从小到大的顺序从1~n进行重新编号,对于身高相等的小朋友,按从左到右的顺序从1~n依次编号(这样做不会影响结果).
b.重新编号方法:先将所有小朋友的身高数组(a[])赋值到另一数组(b[])并进行排序(从小到大),对每一个a[i],去b[]里面二分查找其相对位置k(下标),并令b[k]--。其中二分查找返回的是最左边的a[i]==b[k]的下标k,b[k]--的目的是保证再次查到b[k]的时候查到的是后一个(这样是不会影响b[k]前面的数的 ,至于为什么看看二分代码就明白了)。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100000 + 100; int n; int a[maxn], b[maxn]; struct node { int id; int higher; int lower; }num[maxn]; int binary(int l, int r, int key) { //返回满足条件的最靠左的下标 int mid; while(l < r) { mid = (l + r) / 2; if(key == b[mid]) { if(b[mid - 1] < key) { return mid; } else { r = mid - 1; } } else if(key > b[mid]) { l = mid + 1; } else { r = mid - 1; } } return l; } void merge(int l, int r) { if(l == r) return; int mid = (l + r) / 2; merge(l, mid); merge(mid + 1, r); int i, j, k; for(i = l, j = mid + 1, k = 0; i <= mid && j <= r; ) { if(a[i] < a[j]) { b[k++] = a[i++]; } else if(a[i] > a[j]){ b[k++] = a[j]; num[a[j]].higher += mid - i + 1; j++; } } while(i <= mid) { b[k++] = a[i++]; } while(j <= r) { b[k++] = a[j++]; } for(i = l, j = 0; i <= r; i++) { a[i] = b[j++]; } } int main() { scanf("%d", &n); int i, j; for(i = 1; i <= n; i++) { num[i].higher = 0; } for(i = 1; i <= n; i++) { scanf("%d", a + i); b[i] = a[i]; } sort(b + 1, b + n + 1); for(i = 1; i <= n; i++) { //重新编号 a[i] = binary(1, n, a[i]); b[a[i]]--; num[a[i]].id = i; } merge(1, n); long long ans = 0, t; int pos, w; for(i = 1; i <= n; i++) { pos = num[i].id; //第i低的小朋友在原队的位置 w = i; num[i].lower = w - 1 - (pos - 1 - num[i].higher); t = num[i].higher + num[i].lower; ans += t * (t + 1) / 2; } printf("%I64d\n", ans); return 0; }
方法二:树状数组求逆序对数
参考博客:http://blog.csdn.net/wr132/article/details/43856905 感谢!!
方法是从上面的博客里学到的,但是个人感觉上述博客虽然方法和代码是正确的,但是在细节方面还是有某些地方没讲太清晰。当然这只是我的一己拙见,我试着把求逆序对数的过程修改得更清楚一点,希望没有画蛇添足吧。
首先必须要明确树状数组的用法和意义,不然是没办法做的,关于树状数组的入门,推荐这个博客,讲得很清晰,感谢! http://www.cnblogs.com/zichi/p/4806998.html
首先声明,为了避免混乱,所有的数组下标都是从1开始,因此在这里我们取树状数组sum的下标从1开始,而小朋友身高却是从零开始的,所以将身高+1 作为树状数组的下标,而1即作为增加值(下文中的身高均指原始身高+1之后的身高)。
STEP ONE: 求每个小朋友前面能和他组成逆序对的小朋友个数
第一次输入3,输入数据量i = 1,得到以下数组:
到这里我们就求出了每个小朋友前面能和他组成逆序对的小朋友个数,下面我们就要求每个小朋友后面的能和他组成逆序对的小朋友个数。
STEP TWO: 求每个小朋友后面的能和他组成逆序对的小朋友个数
这次我们将sum数组置零,然后从后往前插入,即先插入1,然后2,然后3。
这样我们每插入完一次,getsum(num[i])得到便是排在第i个小朋友后面并且比第i个小朋友矮的小朋友个数,这就是我们想求的排在第i个小朋友后面的能和他组成逆序对的小朋友个数。所以直接利用这个结果更新b[i]即可。
最终我们得到b[1] = 2,b[2] = 2, b[3] = 2。
这样我们就求出了每个小朋友的逆序对数,也就是他们最少需要交换几次,后面就直接按要求求和即可。注意要用long long。
但是上面的例子是小朋友身高都不同的情况,如果有多个小朋友身高相同,则需要多一步处理。
身高相同的处理:
在step one中,我们想利用i - getnum(num[i]) - 1 求得排在第i个小朋友前面并且比他高的个数,但是仔细一想,getnum(num[i])求得的仅仅是比他矮的人的个数,所以如果还有x个人和i小朋友身高相等,那么i - getnum(num[i]) - 1求得的便是排在第i个小朋友前面并且比他高的个数+x,所以我们应该在最后减去这个x(如果没人和他身高相等的话x即为0)。那么现在的问题就是这个x如何求呢?我们注意到getsum(num[i])求得的是比i矮的人的个数也就是身高小于等于num[i]的个数(注意此时i的身高应为num[i] + 1),那么getsum(num[i] + 1)求得的是身高小于等于num[i] + 1的个数也就是比i矮或者和i身高相等的个数,这样的话 x 就等于getsum(num[i] + 1) - getsum(num[i]) - 1。
而在step tow中,即使存在身高相同的小朋友,我们求得的getsum(num[i])依然是排在第i个小朋友后面并且比第i个小朋友矮的小朋友个数,所以不用处理。
解释就到这里,具体见代码,祝参加蓝桥杯的同学都能取得好成绩!
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100000 + 100; const int maxh = 1000000 + 100; int num[maxn], sum[maxh], b[maxh], n; int lowbit(int x) { return x & (- x); } void update(int index, int x) { int i; for(i = index; i <= maxh; i += lowbit(i)) { sum[i] += x; } } int getsum(int index) { int i; int res = 0; for(i = index; i > 0; i -= lowbit(i)) { res += sum[i]; } return res; } int main() { scanf("%d", &n); memset(sum, 0, sizeof(sum)); int i, j; for(i = 1; i <= n; i++) { scanf("%d", num + i); update(num[i] + 1, 1); b[i] = i - getsum(num[i]) - 1; b[i] -= getsum(num[i] + 1) - getsum(num[i]) - 1; //身高相同的处理 } memset(sum, 0, sizeof(sum)); for(i = n; i > 0; i--) { update(num[i] + 1, 1); b[i] += getsum(num[i]); } long long ans = 0; for(i = 1; i <= n; i++) { ans += (long long)b[i] * (b[i] + 1) / 2; } printf("%I64d\n", ans); return 0; }