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
", 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
",ans);//结果会超int32
42     }
43     return 0;
44 }
原文地址:https://www.cnblogs.com/gj-Acit/p/3250525.html