---
# System prepended metadata

title: 演算法課程題解 - 分治法
tags: [SCIST 演算法 題解]

---

# 演算法課程題解 - 分治法

# 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 演算法 題解`