題目大意就是說幫你給一些(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 }
