ALGO[0]

基礎演算法

我發現我好像沒有自我介紹過

剩下的大家應該不感興趣還是跳過好了

我是教完基礎語法再來教基礎演算法的講師

關於優節

你就可以拿到 not only 修課證明 but also 優秀結業證書了

並且在最後的小社賽達標

其實只要把 codeforces 我每個 problemset 寫到八成

欸不是各位 我還沒發公告就那麼多了是怎樣 都在偷卷喔

啊等等題目點不開正常

我要先問一下你各位 codeforces handle 再幫你們加進來

之後的課程都很硬 請斟酌服用

讓我們開始ㄅ

INDEX

Enumeration

Pruning(剪枝)

Greedy

Enumeration

枚舉

1. Subset 子集

2. Permutation 排列

3. 8 queens problem 八皇后問題

Enumeration

Enumeration

枚舉

其實就是暴搜

for (int i = 1; i <= n; i++) {
	cout << i << ' ';
}

問你 

1 \sim n

的數字有哪些

舉個最簡單的例子

註:

以後如果左上角是灰色代表程式碼片段

如果是彩色就是完整可直接執行的程式

Enumeration

Subset

子集

顯然他不會只那麼簡單

問你集合 

0 \sim n - 1

的子集合有哪些

\{0, 1, 2\}
\phi
\{0\}, \{1\},\{2\},\{0, 1\},\{1, 2\},\{0, 2\},\{0, 1, 2\}

的子集有:

(空集合)

2^3 = 8

有兩種方式可以生成子集

遞迴&位元運算

Enumeration

Method 1

遞迴

vector<int> subset;
void search(int k) {
	if (k == n) {
    	 // process subset
    } else {
    	search(k + 1);
        subset.push_back(k);
        search(k + 1);
        subset.pop_back();
    }
}

search(0)

search(1)

search(1)

search(2)

search(2)

search(2)

search(2)

search(3)

search(3)

search(3)

search(3)

search(3)

search(3)

search(3)

search(3)

\phi
\{2\}
\{1\}
\{1, 2\}
\{0\}
\{0, 2\}
\{0, 1\}
\{0, 1, 2\}

Enumeration

Method 2

位元運算

for (int s = 0; s < (1 << n); s++) {
	vector<int> subset;
    for (int i = 0; i < n; i++) 
   		if (s & (1 << i)) subset.push_back(i);
        
    // process subset
}
000_{(2)}
001_{(2)}
010_{(2)}
011_{(2)}
0
1
s=
3
2
subset=
\phi
\{2\}
\{1\}
\{1, 2\}
\{0\}
\{0, 2\}
\{0, 1\}
\{0, 1, 2\}
subset=
100_{(2)}
101_{(2)}
110_{(2)}
111_{(2)}
5
s=
7
6
4
s=
subset=

Enumeration

Permutation

排列

問你 

0 \sim n - 1

有幾種排列方式

\{0, 1, 2\}
(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)

的排列有:

3! = 6

也是有兩種方式可以生成所有排列

遞迴&內建函式

Enumeration

Method 1

遞迴

vector<int> permutation;
vector<bool> chosen(n);

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();
        }
    }
}

複雜度為:

O(n \times n!)

Enumeration

Method 2

內建函式

vector<int> permutation;
for (int i = 0; i < n; i++) {
	permutation.push_back(i);
}

do {
	// process permutation
} while (next_permutation(permutation.begin(),permutation.end()));

<algorithm>

裡面

next_permutation

(需要先排序)

若沒有排序則無法遍歷所有的排列

所以使用前可以先

sort

Enumeration

題目

八皇后問題

8 \times 8

的棋盤裡面放上八個皇后,

且不互相攻擊,有幾種擺法?

(每個黃后的橫行縱行斜線方向都不能出現其他的皇后)

n \times n

的棋盤裡面放上

後來衍生為

n

個皇后

對於

n=4

為一合法解

不合法

Enumeration

解法

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;
	}
}

枚舉所有排列

1
1
1
1
0
0
0
0
2
2
2
2
3
3
3
3
2
1
3
4
0
1
2
3
2
3
4
5
6
5
4
3
3
4
2
1
3
2
1
0
5
4
3
2
3
4
5
6
column
diag1
diag2

數字為索引

Enumeration

習題

Pruning

剪枝

1. Grid Paths

2. Optimization 優化

Pruning

題目

問你有幾種可能可以從

7 \times 7

格子

的左上角走滿整個平面後到達右下角?

111712

全部共

的其中一種解

使用遞迴枚舉

執行時間:

483

遞迴次數:

760

億次

Pruning

Optimization 1

第一次優化

發現兩者對稱

觀察以下兩個圖形

第一步選擇往下(或右),將答案

\times 2

執行時間:

244

遞迴次數:

380

億次

Pruning

Optimization 2

第二次優化

發現他沒走滿

觀察以下圖形

提前到達直接終止

執行時間:

119

遞迴次數:

200

億次

Pruning

Optimization 3

第三次優化

觀察以下圖形

發現他撞牆走不滿

執行時間:

1.8

遞迴次數:

2.21

億次

撞牆且可左右轉,直接終止

Pruning

Optimization 4

第四次優化

不能往前但可左右,直接終止

觀察以下圖形

執行時間:

0.6

遞迴次數:

6900

萬次

0.6

483

秒!

\rightarrow 0.6

Enumeration

習題

如果吃 TLE 可以把 vector 換成傳統陣列

Greedy

貪心/貪婪

1. Coin Problem

2. Scheduling

3. Tasks & Deadlines

Greedy

Greedy

每次選擇都往會構成最佳解的方向走

而最終形成全局最優解

但往往很難證明他是對的

你有無限個幣值為

1,\ 2,\ 5,\ 10,\ 20,\ 50,\ 100,\ 200

的硬幣

最少能用幾個硬幣湊出指定的金額?

若想湊成

8

需要

5 + 2 + 1

3

Greedy

決策樹

剛剛的選擇可以畫成以下的決策樹

每個節點都代表著一個決策

把所有可能枚舉出來

每個分岔都代表不同選擇

Greedy 就是找有最佳解的子樹一直走下去

total = 8
5
2
1
2
1
2
1
2
1
2
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

Greedy

Proof

可以先觀察:

其實只需要從最大的開始拿就好

可以得到:

面額為

1,\ 5,\ 10,\ 50,\ 100

只會出現一次,

因為如果出現兩次,可以使用更大的面額取代

2,\ 20

最多出現兩次

(因為三個

2

可以用

2,\ 20
5 + 1

代替,      同理)

20
x

的硬幣,

x

小的硬幣來構成面額為

x

或更大的金額

對於每一個面額為

我們沒辦法用比

(1 + 2 + 2 + 10 + 20 + 20 + 50 = 99 < 100)

所以我們證明了 Greedy 很難證明

寫題目的時候可以使用感性理解

其實就只是知道他是對的但懶得證明而已

Greedy

Movie Festival

要怎麼 Greedy?

給定

n

個時段,問不重疊的線段最多能選幾個?

考慮以下四個線段:

最多選兩個

Greedy

Greedy

how

反例

反例

反例

證明

當我們首先選擇一個結束時間晚於最早結束的情況

之後最多只會有相同數量的選擇

因此,

選擇一個結束時間較晚的事件永遠無法產生更好的解答

1. 最短

2. 最早開始

3. 最早結束

3. 最早結束

Greedy

Tasks & Deadlines

total= -10

給定

n

個時段和死線,每完成一個能獲得

死線    完成時間的分數,問最多能獲得幾分?

-

喔對這可以是負數 死線戰士應聲倒地

task
duration
deadline
A
B
C
D
4
3
2
4
2
5
7
5
0
5
10
C
B
A
D
5\ points
0\ points
-7\ points
-8\ points

Greedy

Greedy

how

根據                 從小到大排序就好了

duration
X
Y
a
b
Y
X

交換之後

b
a

證明

少了   ,    多了   ,總共增加

X
b
Y
a
a-b>0

小的一定在前面

\rightarrow duration

Greedy

Stick Division

若要將長度為    的木棍分成              三段:

8
2,\ 3,\ 3

會發現在整顆樹越下面的數字加了越多次

可以把題目變成將需要的長度合併

剩下的算法設計&證明就留給各位了

長度

的木棍,要切成

x
n

段已知的長度,

每次切割成本為原始木棍的長度,問最小成本?

最佳解可繪製成以下樹狀圖(不是決策樹)

8
3
5
3
2

成本為

(3 + 2) + (3 + 5) = 5 + 8 = 13

小小社賽

基礎演算法

By keaucucal

基礎演算法

  • 393