# 演算法課程題解 - 分治法 # UVa 839 ## 題目 http://domen111.github.io/UVa-Easy-Viewer/?839 ![](https://i.imgur.com/oTHz46J.png) 我們都知道秤子有力矩,如果要讓秤子維持平衡的狀態,則左右兩側的力矩必須要相同 現在有很多秤子互相連接再一起,如下圖 ![](https://i.imgur.com/dmjURhR.png) 告訴你每個天平的 `左力矩(Dl)` `右力矩(Dr)` `左物重(Wl)` `右物重(Wr)` 如果 Dl 或是 Dr 為 0 則表示在左側或是右側接的是秤子,接下來會依序告訴你左側或是右側的秤子狀態 問全部的天秤是不是都能維持平衡的狀態 ## 想法 By Koios 一個天平要是平衡的,就必須要讓 `左總物重*左力臂` = `右總物重*右力臂` 輸入會依照由上而下、由左而右的順序輸入進來,所以我們可以用遞迴的方式找出每個子秤子的重量,再來判斷秤子的左右是否平衡 ### 程式碼 ```cpp= //By Koios1143 #include<iostream> using namespace std; int t; bool ans; int solve(){ int Wl, Dl, Wr, Dr; cin>>Wl>>Dl>>Wr>>Dr; // 先找出子秤子的重量 if(Wl == 0){ Wl = solve(); } if(Wr == 0){ Wr = solve(); } // 再判斷是否平衡 if(Wl*Dl != Wr*Dr){ ans = false; } // 將當前秤子的總重量回傳 return Wl + Wr; } int main(){ cin>>t; for(int i=0 ; i<t ; i++){ ans = true; if(i) cout<<"\n"; solve(); if(ans){ cout<<"YES\n"; } else{ cout<<"NO\n"; } } return 0; } ``` # UVa 10810 ## 題目 http://domen111.github.io/UVa-Easy-Viewer/?10810 經典問題,逆序數對 給一串序列,問 bubble sort 由小到大排序需要交換幾次 ## 想法 By Koios 要問 bubble sort 的交換次數,等同在問 序列中有多少組 $i, j \quad i<j$ 使得 $a_i > a_j$,也就是所謂的逆序數對 因為唯有前面比較大的數字必定會和後面比較小的數字交換 試著把序列切一半,會發現問題變成問 1. 在左半邊的逆序數對 2. 在右半邊的逆序數對 3. 橫跨左右兩側的逆序數對 如此一來,左側、右側也可以看做是新的子問題,等同分別在問左側及右側這兩個序列的逆序數對量,那麼我們就可以把問題切割下去遞迴求解 至於橫跨左右兩側的就在合併的時候處理 這樣的架構跟 merge sort 其實是類似的 如果就跟著 merge sort 的架構走,會發生什麼事呢? 當我要合併兩區域時,兩側的序列都是已經由小到大排序好的 那麼當我要找有多少組逆序數對時,我們就只需要去看 **右側的元素是否小於左側** 如果是,那麼右側元素也必定會小於左側剩餘的所有元素,反過來說,這些就是我們要找的逆序數對 因此,整個問題變成,在序列 merge sort 合併過程當中,右側元素小於左側元素時,左側元素剩餘數量總合為多少 ### 程式碼 ```cpp= //By Koios1143 #include<iostream> using namespace std; int n, arr[500010], tmp[500010]; long long ans; void merge(int L, int R){ int mid = (L+R)>>1; for(int i=L, j=mid+1, k=L ; k<=R ; k++){ if(i>mid){ tmp[k] = arr[j++]; } else if(j>R){ tmp[k] = arr[i++]; } else if(arr[i] > arr[j]){ // 當右側元素較小,則左側剩餘所有元素都可與 j 形成逆序數對 ans += mid+1-i; tmp[k] = arr[j++]; } else{ tmp[k] = arr[i++]; } } for(int i=L ; i<=R ; i++){ arr[i] = tmp[i]; } } // [L, R] void mergesort(int L, int R){ if(L == R) return; int mid = (L+R)>>1; mergesort(L, mid); mergesort(mid+1, R); merge(L, R); } int main(){ while(cin>>n && n){ ans = 0; for(int i=0 ; i<n ; i++){ cin>>arr[i]; } mergesort(0, n-1); cout<<ans<<"\n"; } return 0; } ``` ### 時間複雜度分析 每筆測資輸入時間複雜度為 $O(n)$ 每筆測資尋找逆序數對時間複雜度為 $O(nlogn)$ 每筆測資總時間複雜度為 $O(n + nlogn)$ # UVa10602 *待檢查* ## 題目 http://domen111.github.io/UVa-Easy-Viewer/?10602 輸入一系列的單字,求輸入字母最少的次數,第一個字串需要最早輸出。 ## 想法: 先排序字串再尋找有最長相同前綴的單字 這樣就可以減少輸入次數 之後可以用暴力解(但很麻煩,也可以儲存開表格檢查 ```cpp= ``` # TOJ 472 ## 題目 https://toj.tfcis.org/oj/pro/472/ 給一個長度 $N$ 的序列, $3 \leq N \leq 10^{6}$ 定義一組順序數對有三個數字 $a_i, a_j, a_k$,並且滿足 $i < j < k$ 且 $a_i < a_j < a_k$ 求序列當中有多少組順序數對 ## 想法 By Koios 比 $a_j$ 還小並且在它左邊的數字的數字乘上比 $a_j$ 還大並且在它右邊的數字就會是以 $a_j$ 為順序數對中間值的數對組數 對於每個數字都找出這兩種數值相乘,其總和就會是答案 要找出有多少在自己右邊並且比自己大的數字可以用 merge sort 由小到大排序來找到 在合併跨兩邊的序列過程當中,可以保證右邊的序列都在自己的右邊 如果在右邊的序列中找到一個比當前左邊元素還大的,那麼右邊剩餘的所有元素都比自己大 找出每個數字有多少在自己右邊右比自己大的數字之後,接下來要找在自己左邊並且比自己小的數字 這同樣可以用 merge sort 找到,不過是由大到小排序 在合併跨兩邊的序列過程當中,可以保證左邊的序列都在自己的左邊 如果在左邊的序列中找到一個比當前右邊元素還小的,那麼左邊剩下的所有元素都比自己大 ### 程式碼 ```cpp= //By Koios1143 #include<iostream> using namespace std; long long n, m, ans=0, bigger[1000005]; pair<long long ,int> arr[1000005], ary[1000005], tmp[1000005]; // 由小到大排序 // 對 ary 序列排序,放在 tmp 序列中 void merge_sort(int L, int R){ if(L==R) return; int mid = (L+R)>>1; merge_sort(L, mid); merge_sort(mid+1, R); for(int i=L, j=mid+1, k=L ; k<=R ; k++){ if(i > mid){ tmp[k] = ary[j++]; } else if(j > R){ tmp[k] = ary[i++]; } else if(ary[i].first < ary[j].first){ // 找到比 ary[i] 還大,並且在右側的元素 bigger[ary[i].second] += R-j+1; tmp[k] = ary[i++]; } else{ tmp[k] = ary[j++]; } } for(int i=L ; i<=R ; i++){ ary[i] = tmp[i]; } } // 由大到小排序 // 對 arr 排序排序,放在 tmp 序列中 void merge_sort2(int L, int R){ if(L == R) return; int mid = (L+R)>>1; merge_sort2(L, mid); merge_sort2(mid+1, R); for(int i=L, j=mid+1, k=L ; k<=R ; k++){ if(i>mid){ tmp[k] = arr[j++]; } else if(j > R){ tmp[k] = arr[i++]; } else if(arr[i].first < arr[j].first){ // 找到比 arr[j] 還小,並且在左側的元素 // 乘上比 arr[j] 還大的元素數量就找到了順序數對 ans += (mid-i+1)*bigger[arr[j].second]; tmp[k] = arr[j++]; } else{ tmp[k] = arr[i++]; } } for(int i=L ; i<=R ; i++){ arr[i] = tmp[i]; } } int main(){ cin>>n; for(int i=0 ; i<n ; i++){ cin>>m; arr[i] = make_pair(m, i); ary[i] = arr[i]; bigger[i] = 0; } merge_sort(0, n-1); merge_sort2(0, n-1); cout<<ans<<"\n"; return 0; } ``` ### 時間複雜度分析 輸入時間複雜度為 $O(n)$ 找答案時間複雜度為 $O(nlogn)$ 總時間複雜度為 $O(n + nlogn)$ ###### tags: `SCIST 演算法 題解`