# Week 7:排序一、二 ###### tags: `S3 公開資源` :::info 時間:03/12 9:00 ~ 17:00 地點:成大計算機與網路中心 **75201** 教室 主題:排序一、二 直播連結:https://youtube.com/live/pE71zOwnWg4?feature=share ::: # 必作題題解 [TOC] ## 二章 - 第七節 <!-- ### [題目](連結) --> ### [UVa 10327 - Flip Sort](http://domen111.github.io/UVa-Easy-Viewer/?10327) 撰寫者:fishhh 實作一次 bubble sort 就好 這個東西有一個專有名詞:逆序數對 最佳複雜度可以在 $O(N\log N)$ 內完成。 :::spoiler 參考程式碼 ```cpp= #include "iostream" using namespace std; int ary[2010]={}; int main(){ int n; while(cin>>n){ for(int i=0;i<n;i++)cin>>ary[i]; bool change = 0; int cnt=0; for(int i=0;i<n;i++){ change = 0; for(int j=0;j<n-i-1;j++){ if(ary[j]>ary[j+1]){ swap(ary[j],ary[j+1]); change = 1; cnt++; } } if(!change)break; } // for(int i:ary)cout<<i<<" "; cout<<"Minimum exchange operations : "<<cnt<<"\n"; } } ``` ::: --- ### [No - Judge 名次查詢](https://hackmd.io/@sa072686/cp/%2Foj2gvDtjT0mfKJVhlmJeng#名次查詢) 撰寫者:Eason 直接由大而小排序,就可以$O(1)$查詢 :::spoiler 合併排序 ```cpp= #include<iostream> using namespace std; long long arr[200005]; void merge (int l, int r){ int mid = (l + r) / 2; if (l == r) return; merge (l, mid); merge (mid + 1, r); int sorted[200005]; int lp = l, rp = mid + 1, p = l; while (lp <= mid && rp <= r){ if (arr[lp] > arr[rp]){ sorted[p] = arr[lp]; lp++; p++; } else{ sorted[p] = arr[rp]; rp++; p++; } } while (lp <= mid){ sorted[p] = arr[lp]; lp++; p++; } while (rp <= r){ sorted[p] = arr[rp]; rp++; p++; } for (int i = l; i <= r; i++){ arr[i] = sorted[i]; } return; } int main(){ int n; cin >> n; for (int i = 0; i < n; i++){ cin >> arr[i]; } merge (0, n - 1); int q; cin >> q; for (int i = 0; i < q; i++){ int temp; cin >> temp; cout << arr[temp - 1] << '\n'; } return 0; } ``` ::: :::spoiler 快速排序 ```cpp= #include<iostream> using namespace std; int arr[200005]; void quick_sort(int l, int r){ if (l >= r) return; int tar = (l + r) / 2; int i = l; for (int ind = l + 1; ind <= r; ind++){ if (arr[ind] > arr[tar]){ i++; swap (arr[i], arr[ind]); } } swap (arr[tar], arr[i]); quick_sort(l, i - 1); quick_sort(i + 1, r); return; } int main(){ ios::sync_with_stdio(0), cin.tie(0); int n; cin >> n; for (int i = 0; i < n; i++){ cin >> arr[i]; } quick_sort(0, n - 1); int q; cin >> q; for (int i = 0; i < q; i++){ int temp; cin >> temp; cout << arr[temp - 1] << '\n'; } return 0; } ``` ::: --- ### [No - Judge 集合比較](https://hackmd.io/@sa072686/cp/%2Fys2z9BntSh-_6mZTIPNy1Q#%E9%9B%86%E5%90%88%E6%AF%94%E8%BC%83) 撰寫者:fishhh 這裡使用 merge sort 完成排序。 :::spoiler ```cpp= #include "iostream" using namespace std; int ary[2010][2]={}; int temp[2010][2]={}; void merge(int l,int r,int ay); void split(int l,int r,int ay){ //這裡是 [l,r] (閉區間) if(l==r)return; int m = (l+r)/2; split(l,m,ay); split(m+1,r,ay); merge(l,r,ay); return ; } void merge(int l,int r,int ay){ int m = (l+r)/2; int pt1=l,pt2=(l+r)/2+1; for(int i=l;i<=r;i++){ if(pt1 > m){ //左半邊已經拿完了 temp [i][ay] = ary[pt2++][ay]; } else if(pt2==r+1){ //右半邊已經拿完了 temp [i][ay] = ary[pt1++][ay]; } else if(ary[pt1][ay]>ary[pt2][ay]){ temp[i][ay] = ary[pt1++][ay]; } else{ temp[i][ay] = ary[pt2++][ay]; } } for(int i=l;i<=r;i++)ary[i][ay]=temp[i][ay]; return ; } int main(){ int n,m; cin>>n>>m; if(n!=m){ cout<<"not equal\n"; return 0; } for(int i=1;i<=n;i++)cin>>ary[i][0]; for(int i=1;i<=m;i++)cin>>ary[i][1]; split(1,n,0); split(1,m,1); for(int i=1;i<=n;i++){ if(ary[i][0]!=ary[i][1]){ cout<<"not equal\n"; // cout<<ary[i][0]<<" "<<ary[i][1]<<"!"; return 0; } } // for(int i=1;i<=n;i++){ // cout<<ary[i][0]<<" "; // } cout<<"equal\n"; return 0; } ``` ::: --- ### [No Judge 種類統計](https://hackmd.io/@sa072686/cp/%2Fys2z9BntSh-_6mZTIPNy1Q#%E7%A8%AE%E9%A1%9E%E7%B5%B1%E8%A8%88) 撰寫者:小麥 先sort再掃描一次並紀錄次數 :::spoiler ```cpp= #include <algorithm> #include <iostream> #include <sstream> #include <vector> using namespace std; vector<long long> arr; void solve() { int n; cin >> n; arr.resize(n); for (int i = 0; i < n; i++) { cin >> arr[i]; } sort(arr.begin(), arr.end()); int counter = 0; int type_counter = 0; stringstream ans; for (int i = 1; i < n; i++) { if (arr[i] != arr[i - 1]) { ans << arr[i - 1] << ": " << counter << " times" << "\n"; counter = 0; type_counter++; } counter++; } ans << arr[n - 1] << ": " << counter << " times" << "\n"; cout << (type_counter + 1) << " type\n" << ans.str() << '\n'; } int main() { solve(); return 0; } ``` ::: --- ### [ZJ b966 - 線段覆蓋長度](https://zerojudge.tw/ShowProblem?problemid=b966) 撰寫者:Koying 講義有提供合併線段的解法,參考程式如下 :::spoiler Code ```cpp= #include <bits/stdc++.h> #define ll unsigned long long using namespace std; pair<int,int> v[10005]; signed main() { int n; cin >> n for(int i=0;i<n;i++) { cin >> v[i].first >> v[i].second; } sort(v,v+n); // 1,2 4,8 5,6 7,9 int a=v[0].first,b=v[0].second; int ans=0; for(int i=0;i<n;i++) { if(v[i].first>b) { ans+=b-a; a=v[i].first; b=v[i].second; } else if((v[i].second)>b) b=v[i].second; } ans+=b-a; cout<<ans<<endl; return 0; } ``` ::: #### 另解 1(只需陣列的解法): 可以觀察到,對於一個被線段覆蓋的區間(如 $[1, 5]$ 就是一個被 $[1, 4], [2, 5]$ 兩線段覆蓋的區間),其起終點一定是一個線段的起終點,因此我們只需要記錄所有線段的起點與終點位置即可。 假設有一個線段 $[1, 4]$,我們在 $1$ 的位置紀錄為 $+1$,代表這個位置多了一條線段,並在 $4$ 的位置紀錄為 $-1$,代表這個位置少了一條線段。 最後,將紀錄的數字做前綴和後得到的結果,就是每個位置的線段覆蓋**厚度**。只要計算 $> 0$ 的塊數就是答案。 時間複雜度:$\mathcal{O}(10^7)$ :::spoiler Code ```cpp= #include <bits/stdc++.h> using namespace std; int x[10005], y[10005]; int cnt[10000005]; signed main() { ios::sync_with_stdio(0); cin.tie(0); int n; cin >> n; for (int i = 0; i < n; i++) { cin >> x[i] >> y[i]; cnt[x[i]]++; cnt[y[i]]--; } int ans = 0; for (int i = 0; i < 10000005; i++) { cnt[i] += cnt[i - 1]; ans += (cnt[i] > 0); } cout << ans << '\n'; } ``` ::: #### 另解 2(使用排序 + struct / pair) 同樣是使用紀錄頭尾的技巧,我們將頭尾座標及狀態包成一個 struct or pair(例如線段 $[1, 4]$ 包成 $(1, +1), (4, -1)$)並用陣列紀錄。 接著我們將這個陣列依照第一個元素(座標)排序,得到的結果便是從左到右的**狀態更新順序**。 接著我們就可以依序對每次狀態更新進行計算,並記錄以下變數: - $now$:上一次更新到哪 - $cur$:現在座標 - $cnt$:線段覆蓋厚度 - $ans$:答案 對於每種狀態更新,只有兩種情況: 1. 原本沒有線段覆蓋,現在多了一個 2. 原本有線段覆蓋,現在多或少了一個 可以發現,能夠修改答案的情況只有第二種,因為在這個座標之前就有線段覆蓋了,所以我們就可以將答案 $ans$ 加上 $cur - now$,並將 $now$ 更新為 $cur$,就可以得到最終的答案 :::spoiler Code ```cpp= #include <bits/stdc++.h> using namespace std; pair<int, int> e[20005]; signed main() { ios::sync_with_stdio(0); cin.tie(0); int n; cin >> n; for (int i = 0; i < n; i++) { int x, y; cin >> x >> y; e[i * 2] = {x, 1}; e[i * 2 + 1] = {y, -1}; } sort(e, e + n * 2); int ans = 0, now = 0, cnt = 0; for (int i = 0; i < n * 2; i++) { if (cnt == 0) { cnt += e[i].second; now = e[i].first; } else { cnt += e[i].second; ans += e[i].first - now; now = e[i].first; } } cout << ans << '\n'; } ``` ::: 兩份解法的時間差異: ![](https://hackmd.io/_uploads/HkZpca513.png) --- ### [Kattis oddmanout - Odd Man Out](https://open.kattis.com/problems/oddmanout) 撰寫者:fishh 排序好之後,相同的兩個就會被放在一起。 可以知道一件事,如果 $ary[i]$ 是那個要被踢出去的,那麼前面一定都會兩兩成對。也就是說,前面的元素個數一定是"偶數個"。 :::spoiler 參考程式碼 ```cpp= #include<iostream> using namespace std; unsigned long long temp[10000],arr[10000],n; void merge(int s,int e){ int h=s,t,mid=(s+e)/2; t=mid+1; for(int d=s;d<=e;d++){ if(t>e){ temp[d]=arr[h]; h++; } else if(h>mid){ temp[d]=arr[t]; t++; } else if(arr[h]>arr[t]){ temp[d]=arr[h]; h++; } else{ temp[d]=arr[t]; t++; } } for(int i=s;i<=e;i++){ arr[i]=temp[i]; } } void spit(int s,int e){ int mid=(e+s)/2,qq; if(s==e){ // cout<<s; return ; } spit(s,mid); spit(mid+1,e); merge(s,e); } int main(){ int n,q; cin>>n; for(int i=1;i<=n;i++){ cin>>q; cout<<"Case #"<<i<<": "; for(int x=0;x<q;x++){ cin>>arr[x]; } spit(0,q-1); for(int x=0;x<q;x+=2){ if(arr[x]!=arr[x+1]){ cout<<arr[x]<<"\n"; break; } } } } ``` ::: --- ### [Kattis recount - Recount](https://open.kattis.com/problems/recount) 撰寫者:fishhh 題目的意思就是要算每個人得到了幾票,輸出票數最高的人 這邊偷下一章會講到的 sort 內建函數來用。 :::spoiler ```cpp= #include "iostream" #include "algorithm" using namespace std; string ary[100010]={}; int main(){ int n=0; string s; while(getline(cin,s)){ if(s=="***")break; ary[n++]=s; } sort(ary,ary+n); int cnt=1,maxcnt=0; bool same=0; string ans=ary[0]; ary[n] = ary[n-1]+".."; for(int i=1;i<=n;i++){ if(ary[i]!=ary[i-1]){ if(cnt>maxcnt){ maxcnt=cnt; same=0; ans = ary[i-1]; } else if(cnt==maxcnt)same=1; cnt=1; } else cnt++; } if(same){ cout<<"Runoff!\n"; } else{ cout<<ans; } } ``` :::