POJ2299Ultra-QuickSort (线段树和归并排序的解法)
题目大意就是说帮你给一些(n个)乱序的数,让你求冒泡排序需要交换数的次数(n<=500000)
此题最初真不会做,我也只是在听了章爷的讲解后才慢慢明白过来的
首先介绍线段树的解法:
我们先将原数组每个值附上一个序号index,再将它排序。如题目的例子:
num: 9 1 0 5 4
index: 1 2 3 4 5
排序后:
num: 0 1 4 5 9
index: 3 2 5 4 1
然后由于排序后num为0的点排在原来数组的第3个,所以为了将它排到第一个去,那就至少需要向前移动两次,同时它也等价于最小的数0之前有2个数比它大(所以要移动两次),将0移到它自己的位置后,我们将0删掉(目的是为了不对后面产生影响)。再看第二大的数1,它出现在原数组的第二个,他之前有一个数比它大所以需要移动一次。这样一直循环下去那么着5个数所需要移动的次数就是:
num: 0 1 4 5 9
次数 2 1 2 1 0
将次数全部要加起来就是最后所需要移动的总次数。
方法章爷是已经告诉我了,但是最初我一直是觉得不好实现。到后来才慢慢、慢慢弄好。方法就是在建一棵树时,不是直接将原来的num放进树里面,而是将它的下标放进树里面,最初每个节点上赋值为1.然后当查找第一个num时,由于是找的下标为3的位置,所以我们就直接找区间[1,3)之间有多少个1(就是求前导和),这里面1的个数就是第一个num=0索要移动的次数,然后我们把0去掉,其实也就是吧下标为3的那个1去掉。这样每个值就依次计算出来了。
当然其实只要是想明白了,不用线段树,直接用树状数组写起来会简便很多。(因为每次只需要计算前导和以及去掉某一个点,是对点的操作)。
这里再讲一下归并排序的方法(对于最基础就没有掌握好的我来说听到他们说归并排序可以解题时,我竟然一团雾水,竟然连归并排序都忘记了),看了一下归并排序的实现过程,其实马上就可以找到思路,由于本题实际上就是要求逆序对(即满足i<j,a[i]>a[j]的数对)的个数。而我们再回顾一下归并排序的过程:
假设回溯到某一步,后面的两部分已经排好序(就是说当前需要归并的两个部分都是分别有序的),假设这两个序列为
序列a1:2 3 5 9
序列a2:1 4 6 8
此时我们的目的就是要将a1和a2合并为一个序列。
由于在没排序前a2序列一定全部都是在a1序列之后的,当我们比较a2的1与a1的2时,发现1<2按照归并的思想就会先记录下a2的1,而这里实际上就是对冒泡排序的优化,冒泡是将a2的1依次与a1的9,5,3,2交换就需要4次,而归并却只有一次就完成了,要怎么去记录这个4呢,实际上由于1比2小而2后面还有4个数,也就是说那我的结果就必须要+4,也就是记录a1序列找到第一个比a2某一个大的数,他后面还余下的数的个数就是要交换的次数。
同时我们看a2的4时,a1中第一个比它大的数是5,5之后共有两个数,那结果就+2,。依次下去就可以计算出结果。但是由于我们任然没有改变归并排序的过程。所以复杂度还是O(nlogn),比上面的线段树要快。
另外,此题有一坑就是结果会超int32,,用__int64
首先是线段树的解法11892 KB 1047 ms
1 #include <stdio.h> 2 #include <string.h> 3 #include <algorithm> 4 #define mem(a) memset(a,0,sizeof(a)) 5 #define MIN(a , b) ((a) < (b) ? (a) : (b)) 6 #define MAXN 500010 7 #define INF 1000000007 8 #define lson k<<1, l, mid 9 #define rson (k<<1)|1, mid+1, r 10 11 using namespace std; 12 13 int Tree[MAXN<<2], index[MAXN], num[MAXN]; 14 int N; 15 16 int cmp(const int i, const int j) 17 { 18 return num[i] < num[j]; 19 } 20 21 void Edit(int k, int l, int r, int num, int value) 22 { 23 Tree[k] += value; 24 if(r==l) return ; 25 int mid = (l+r) >> 1; 26 if(num <= mid) Edit(lson, num, value); 27 else Edit(rson, num, value); 28 } 29 30 int Search(int k, int l, int r, int L, int R)//L,R是要找的区间 31 { 32 if(L<=l && r<=R) return Tree[k]; 33 int mid = (l+r) >> 1; 34 int ans = 0; 35 if(L <= mid) ans += Search(lson, L, R); 36 if(R > mid) ans += Search(rson, L, R); 37 return ans; 38 } 39 40 int main() 41 { 42 while(~scanf("%d", &N) && N) 43 { 44 mem(Tree); mem(num); mem(index); 45 for(int i=1;i<=N;i++) 46 { 47 scanf("%d", &num[i]); 48 Edit(1, 1, N, i, 1); 49 index[i] = i; 50 } 51 sort(index+1, index+N+1,cmp); 52 long long ans = 0; 53 for(int i=1;i<=N;i++) 54 { 55 ans += (Search(1, 1, N, 1, index[i])-1); 56 Edit(1, 1, N, index[i], -1); 57 } 58 printf("%I64d\n", ans); 59 } 60 return 0; 61 }
然后是归并排序(只有注释位置有改动,其他的无变化)4076K 438MS
1 #include <stdio.h> 2 #include <string.h> 3 #define mem(a) memset(a, 0, sizeof(a)) 4 5 int N, A[500010], T[500010]; 6 __int64 ans; 7 8 void Merg_Sort(int x,int y) 9 { 10 if(y-x<=1) return ; 11 int mid = x + (y-x)/2; 12 Merg_Sort(x,mid); 13 Merg_Sort(mid,y); 14 int p = x, q = mid, i=x; 15 while(p<mid || q<y) 16 { 17 if(q>=y || (p<mid && A[p] <= A[q])) T[i++] = A[p++]; 18 else//else的条件是(p==mid || A[q] < A[p]) 19 { 20 if(p<mid) ans+=(mid-p);//由于是p<mid,所以此时也就是相当于 A[q] < A[p] 21 T[i++] = A[q++]; //上面同时A[p]是第一个<A[q]的数,所以+后面还有的数(mid-p) 22 } 23 } 24 for(i=x;i<y;i++) 25 { 26 A[i] = T[i]; 27 } 28 } 29 30 int main() 31 { 32 while(~scanf("%d", &N) && N) 33 { 34 mem(A); mem(T); 35 for(int i=0;i<N;i++) 36 { 37 scanf("%d", &A[i]); 38 } 39 ans = 0; 40 Merg_Sort(0,N); 41 printf("%I64d\n",ans);//结果会超int32 42 } 43 return 0; 44 }