owned this note
owned this note
Published
Linked with GitHub
# [AIdrifter CS 浮生筆錄](https://hackmd.io/@iST40ExoQtubds5LhuuaAw/rypeUnYSb?type=view#Competitive-Programmer%E2%80%99s-Handbookb) <br>Competitive Programming's Handbook <br> Ch5 :Complete Search
# Complete Search 窮舉法
- 常見有暴搜法 (brute force),列舉 (straight-forward))
- 如果搞不定在讓**greedy algorithms** or **dynamic programming**出馬。
## Generating subsets
先從如何簡單產生set有可能開始 - **組合**。
for example, the subsets of $\{0,1,2\}$ are
$\emptyset$, $\{0\}$, $\{1\}$, $\{2\}$, $\{0,1\}$, $\{0,2\}$, $\{1,2\}$ and $\{0,1,2\}$.
### Recursive
- `n = 3`, k自0開始。
```cpp
void search(int k) {
if (k == n) {
// process subset
} else {
search(k+1);
subset.push_back(k);
search(k+1);
subset.pop_back();
}
}
```
```cpp
int n = 3;
vector<int> subset;
void search(int k) {
cout<<" search(" << k<< ")";
if (k == n) {
cout<<endl;
for(auto s: subset)
cout<<s<<" ";
cout<<endl;
} else {
search(k+1);
cout<<" push(" << k<< ")";
subset.push_back(k);
search(k+1);
cout<<" pop()";
subset.pop_back();
}
}
```
```shell
search(0) search(1) search(2) search(3)
push(2) search(3)
2
pop() push(1) search(2) search(3)
1
push(2) search(3)
1 2
pop() pop() push(0) search(1) search(2) search(3)
0
push(2) search(3)
0 2
pop() push(1) search(2) search(3)
0 1
push(2) search(3)
0 1 2
pop() pop() pop()
```
![](https://i.imgur.com/wuh9omQ.png)
### Bit representation
For example, the bit representation of 25(b)
is 11001, which corresponds to the subset $\{0,3,4\}$.
```cpp
for (int b = 0; b < (1<<n); b++) {
vector<int> subset;
for (int i = 0; i < n; i++) {
if (b&(1<<i)) subset.push_back(i);
}
}
```
## Generating permutations
### Recursive
```cpp
void search() {
if (permutation.size() == n) {
// process permutation
} else {
for (int i = 0; i < n; i++) {
if (chosen[i]) continue;
chosen[i] = true;
permutation.push_back(i);
search();
chosen[i] = false;
permutation.pop_back();
}
}
}
```
### next_permutation
```cpp
vector<int> permutation;
for (int i = 0; i < n; i++) {
permutation.push_back(i);
}
do {
// process permutation
} while (next_permutation(permutation.begin(),permutation.end()));
```
## Backtracking 回溯法
- 回溯算法知道回頭:當一條路不同的時候,它會退**回到上一個岔路**,而不是從頭開始。
Consider the problem of calculating the number
of ways $n$ queens can be placed on an $n \times n$ chessboard so that
no two queens attack each other.
For example, when $n=4$, there are two possible solutions:
![](https://i.imgur.com/2LtETLg.png)
At the bottom level, the three first configurations are illegal(在第二排,前三個皇后都互吃), because the queens attack each other. However, the fourth configuration is valid and it can beextended to a complete solution by placing two more queens to the board(在第二排的第4個是合法的,他還可以變最後解). There is only one way to place the two remaining queens
![](https://i.imgur.com/ozYwJhi.png)
```cpp
void search(int y) {
if (y == n) {
count++;
return;
}
for (int x = 0; x < n; x++) {
if (column[x] || diag1[x+y] || diag2[x-y+n-1]) continue;
column[x] = diag1[x+y] = diag2[x-y+n-1] = 1;
search(y+1);
column[x] = diag1[x+y] = diag2[x-y+n-1] = 0;
}
}
```
he array $\texttt{column}$ keeps track of columns that contain a queen,
and the arrays $\texttt{diag1}$ and $\texttt{diag2}$ keep track of diagonals.
It is not allowed to add another queen to a column or diagonal that already contains a queen.
For example, the columns and diagonals of the $4 \times 4$ board are numbered as follows:
![](https://i.imgur.com/t6ufJWO.png)
## Pruning the search
- 回溯算法:當一條路不同的時候,它會退**回到上一個岔路**,而不是從頭開始;
- 回溯算法 + **剪枝**:當發現一條路有問題的時候,**它就會放棄走下去**,這樣就可以避免走一些不必要的路。
![](https://i.imgur.com/mBeVX2X.png)
原文網址:https://kknews.cc/news/mkr6gv9.html
We can often optimize backtracking by pruning the search tree.
The idea is to add ''intelligence'' to the algorithm
so that it will notice as soon as possible
**if a partial solution cannot be extended to a complete solution.**
Let us consider the problem
of calculating the number of paths
in an $n \times n$ grid from the upper-left corner
to the lower-right corner
從左上到右下, 在7*7的gird下,如果又不要**碰撞**,一共有111712的走法。
下面會介紹各種解法。
![](https://i.imgur.com/Ip1NmYg.png)
### Basic Algorithm
- 直接用backtracking 硬幹。
- running time: 483 seconds
- number of recursive calls: 76 billion
### Optimization 1
利用對稱的概念,$down = right$ , $left = up$。
In any solution, we first move one step down or right.
There are always two paths that are symmetric
![](https://i.imgur.com/sDbbZ1c.png)
- running time: 244 seconds
- number of recursive calls: 38 billion
### Optimization 2
如果沒有走完全程,直接到終點(右下),直接放棄這個解,不用再亂走。
![](https://i.imgur.com/bzAkdTl.png)
- running time: 119 seconds
- number of recursive calls: 20 billion
### Optimization 3
如果撞到牆壁後,把gird拆成兩邊,而另外一邊無法訪問完,直接pruning這個解。
![](https://i.imgur.com/SMTLp6y.png)
- running time: 1.8 seconds
- number of recursive calls: 221 million
### Optimization 4
如果撞到自己走過的路(和貪食蛇很像),直接pruning當下解。
![](https://i.imgur.com/k9k2ktG.png)
- running time: 0.6 seconds
- number of recursive calls: 69 million
### Conclusion
- the running time of the original algorithm was 483 seconds, and now after the optimizations, the running time is only 0.6 seconds
- This is a usual `phenomenon` in backtracking, because the search tree is usually large and even simple observations can effectively prune the search.
- 對於search tree來說,越前面的step如果可以控制,可以少走很多冤路,所以盡量想辦法往前pruning。
Especially useful are optimizations that occur during the **first steps** of the algorithm,
i.e., at the top of the search tree.
## 剪枝法 vs 回朔法 vs 分支與限制
- 回溯 Backtracking
- 採用 “深先搜尋法” (Depth-First Search; DFS) 對狀態空間樹中每一個節點進行檢查
- 為**遞迴**的應用概念,因此可利用**Stack**保存走訪過程中間所走過的點。
- 分枝與限制 Branch and Bound
- 策略採用 “廣先搜尋法” (Breadth-First Search; BFS) 對狀態空間樹中每一個節點進行檢查
- 為**迴圈**的應用概念,因此可利用**Queue**保存走訪過程中間所走過的點
上述的兩個策略,皆透過 “邊界函數” (Bounding Function) 來刪除一些不必要的子樹搜尋動作,以提昇搜尋效率。
- 剪枝
- 當搜尋到 “不可行” 的節點 (即:沒前途(nonpromising)節點) 時,則不用再去搜尋該節點以下之所有分枝節點。 此即為修剪 (**Pruning**)
- 當搜尋到 “可行” 的節點 (即:有前途(promising)節點) 時,則可以再續繼往下搜尋該節點以下之分枝節點。
- 可以得到修剪過的狀態空間樹 (Pruned State Space Tree)。
## Meet in the middle
**把 seach sapce 拆成兩塊,最後在合併**
is a technique
where the search space is divided into
two parts of about equal size.
A separate search is performed
for both of the parts,
and finally the results of the searches are combined.
可以有效把exponential下降,是個非常好的方法。
we can turn a factor of $2^n$
into a factor of $2^{n/2}$ using the meet in the
middle technique.
### Question: Does the sum exist in array ?
題目給一陣列$[2,4,5,9]$,令$x=15$,要如何判斷陣列中元素可否組成 $15$ 呢?
$15 = 2 + 4 + 9$
如果採用列舉出set的全部組合(Complete Search),需要花掉$O(2^n)$的時間。
For example, given the list $[2,4,5,9]$ and $x=15$,
we can choose the numbers $[2,4,9]$ to get $2+4+9=15$.
However, if $x=10$ for the same list,
it is not possible to form the sum.
A simple algorithm to the problem is to
go through all subsets of the elements and
check if the sum of any of the subsets is $x$.
The running time of such an algorithm is $O(2^n)$,
because there are $2^n$ subsets.
**Meet in the middle** : 把陣列拆成$A=[2,4]$ and $B=[5,9]$.創造兩陣列 :
$S_A=[0,2,4,6]$ and $S_B=[0,5,9,14]$
再從兩陣列中去找組合,最後找到 $S_A$ ($6 = 2 +4$) + $S_B$($9$) = 15
時間複雜度從 $O(2^n)$ 降成 $2*O(2^{n/2})$
# Reference
backtracking:
http://programming-study-notes.blogspot.com/2014/03/backtracking.html
Complete Search:
https://m80126colin.github.io/blog/articles/%E7%BF%BB%E8%AD%AF/usaco/usaco-1-2-1-text/
回朔法:
https://kknews.cc/zh-tw/news/mkr6gv9.html
# Note
類似 $n!$ 種問題還有這些:
- N皇后(N-Queen)問題
- 旅行銷售員問題(Traveling Salesman Problem; TSP)
- 漢米爾頓迴路(Hamiltonian Circuits)
- 圖形著色(Graph-Coloring)