-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
ข้อนี้ให้ค่าอุณหภูมิในตาราง $N \times N$ $(N \leq 20)$ และกำหนดว่าจากช่องใดๆ จะสามารถขยับไปช่องรอบๆ ที่มีอุณหภูมิที่สูงกว่าเท่านั้น โดยมีบางช่องที่ค่าอุณหภูมิเป็น 100 ซึ่งจะเข้าไม่ได้เลย | ||
|
||
### แนวคิด | ||
|
||
สำหรับข้อนี้เราสามารถมองว่าแต่ละช่องของเป็นตารางที่ไม่ได้มีค่าอุณหภูมิเป็น 100 เป็น Node และระหว่างสองช่องที่ติดกันจะมี Edge ที่มีทิศทางจากช่องที่มีอุณหภูมิน้อยกว่าไปยังช่องที่มีอุณหภูมิมากกว่า | ||
|
||
จากนั้นหากทำ State Space Search เพื่อหาว่า Node ใดในตารางบ้างที่มี Path ที่เป็นไปได้จากช่องเริ่มต้นและเอาอันที่มีอุณหภูมิสูงสุดจะได้คำตอบที่ต้องการ | ||
|
||
เช่นในตัวอย่างประกอบในโจทย์สามารถวาด Edge ได้ดังในรูป | ||
|
||
![](../media/1061/0.png) | ||
|
||
### State Space Search | ||
|
||
State Space Search (การค้นหาในปริภูมิสถานะ) เป็นรูปแบบขั้นตอนวิธีที่จะแทน State (สถานะ) เป็น Node ใน Graph โดย Node $A$ จะมี Edge ไปยัง Node $B$ ก็ต่อเมื่อสามารถเปลี่ยนจาก State $A$ ไป $B$ ได้ในหนึ่งขั้น ใน State Space Search เราจะเริ่มจาก Node ใดๆ แล้วพิจารณา Node รอบข้างที่มี Edge เชื่อมเพื่อพิจารณาแต่ละ State ที่สามารถไปถึงจาก State เริ่ม สำหรับข้อนี้จะต้องหา State ที่มีค่าอุณหภูมิสูงสุด | ||
|
||
รูปประกอบต่อไปนี้เป็นตัวอย่างของ State Space | ||
|
||
![](../media/1061/1.png) | ||
|
||
ในตัวอย่างนี้จะเห็นได้ว่า State $B$ สามารถไป $D$ และ $D$ กับ $E$ สามารถไปมาซึ่งกันและกัน | ||
|
||
ในการทำ State Space Search จะมีวิธีพื้นฐานหลักๆ สองแบบคือ Depth First Search (DFS) กับ Breadth First Search (BFS) | ||
|
||
#### DFS | ||
|
||
ใน DFS จะทำการค้นหาในรูปแบบ Recursive โดยสำหรับแต่ละ State ที่พิจารณา จะ DFS ลงไปใน Node ที่มี Edge ที่ยังได้ไม่ถูกพิจารณา | ||
|
||
หากทำ DFS จาก State $A$ ในตัวอย่างก่อนหน้านี้จะได้ดังในรูป (ลูกศรสีเขียวแทนการเรียก DFS แบบ Recursive และลูกศรสีแดงแทนการ Return กลับมา) | ||
|
||
![](../media/1061/2.png) | ||
|
||
ในตัวอย่างนี้จะพิจารณา State ในลำดับ $A,B,D,E,C,F,G$ | ||
|
||
เมื่อนำมาใช้กับข้อนี้จะได้โค้ดดังนี้ | ||
|
||
```cpp | ||
int M; | ||
int T[22][22]; | ||
int visited[22][22]; | ||
|
||
int dfs(int x, int y) { | ||
visited[x][y] = 1; // visited[x][y] จะแทนว่าช่อง x,y ถูกพิจารณาแล้ว | ||
|
||
int ret = T[x][y]; | ||
|
||
int dx[4] = {0, 0, -1, 1}; | ||
int dy[4] = {1, -1, 0, 0}; | ||
for (int u = 0; u < 4; u++) { // พิจารณา 4 ทิศทาง | ||
int ux = x + dx[u]; | ||
int uy = y + dy[u]; | ||
|
||
if (1 <= ux && ux <= M && 1 <= uy && uy <= M && !visited[ux][uy] && | ||
T[ux][uy] != 100 && | ||
T[ux][uy] > T[x][y]) // ช่องใหม่ต้องอยู่ในกรอบ ยังไม่ถูกพิจารณา และ | ||
// ต้องมีอุณหภูมิที่มากกว่าช่องปัจจุบันและไม่ใช่ 100 | ||
ret = max(dfs(ux, uy), ret); // ค่าที่มากที่สุดจะเป็นค่าที่มากสุดของช่องนี้หรือช่องที่สามารถไปจากช่องนี้ | ||
} | ||
|
||
return ret; | ||
} | ||
``` | ||
ในโค้ดตัวอย่างนี้จะพิจารณาแต่ละช่องที่อยู่ด้าน บน ล่าง ซ้าย หรือ ขวา ว่าเข้าเงื่อนไขที่จะสามารถไปจากช่องปัจจุบันไหมและหากสามารถไปจะเรียก DFS แบบ Recursive | ||
DFS จะใช้ Memory ใน Stack ตามความยาวของ Path ที่ทำ Recursion ซึ่งสำหรับข้อนี้เป็นได้อย่างมาก $\mathcal{O}(M^2)$ และจะใช้ Time Complexity ตามจำนวน Node และ Edge ที่ถูกพิจารณาซึ่งสำหรับข้อนี้เป็น $\mathcal{O}(M^2)$ เช่นกัน | ||
#### BFS | ||
สำหรับ BFS จะต่างจาก DFS ตรงที่เมื่อเจอ Edge ที่ไปได้แล้วจะไม่นำมาพิจารณาทันทีแต่ละนำเข้า Queue ก่อน โดยการพิจารณาแต่ละ Node จะเรียงตามลำดับใน Queue | ||
ตัวอย่างดังภาพ | ||
![](../media/1061/3.png) | ||
ในตัวอย่างนี้จะพิจารณาเป็นลำดับ $A,B,C,D,E,F,G$ ตามลูกศรสีเขียว | ||
โค้ด BFS ตัวอย่างสำหรับข้อนี้ | ||
```cpp | ||
int M; | ||
int T[22][22]; | ||
int bfs(int x, int y) { | ||
int queued[22][22]; | ||
queued[x][y] = 1; | ||
std::deque<std::pair<int, int>> q; // Queue สำหรับการเก็บแต่ละ Node | ||
q.push_back(std::make_pair(x, y)); // นำ Node เริ่มต้นเข้า Queue | ||
int ret = T[x][y]; | ||
while (!q.empty()) { | ||
int qx = q[0].first, qy = q[0].second; | ||
q.pop_front(); // นำ Node แรกมาพิจารณาและเอาออกจาก Queue | ||
ret = std::max(ret, T[qx][qy]); | ||
int dx[4] = {0, 0, -1, 1}; | ||
int dy[4] = {1, -1, 0, 0}; | ||
for (int u = 0; u < 4; u++) { | ||
int ux = qx + dx[u]; | ||
int uy = qy + dy[u]; | ||
if (1 <= ux && ux <= M && 1 <= uy && uy <= M && !queued[ux][uy] && | ||
T[ux][uy] != 100 && T[ux][uy] > T[qx][qy]) { | ||
q.push_back(std::make_pair(ux, uy)); // ใส่ Node ใหม่ที่เจอเข้า Queue | ||
queued[ux][uy] = 1; | ||
} | ||
} | ||
} | ||
return ret; | ||
} | ||
``` | ||
|
||
สังเกตว่าในโค้ดนี้จะเรียก BFS เพียงรอบเดียวต่างจาก DFS โดยจะนำ Node มาใส่ Queue แทนการใช้ Recursion | ||
|
||
BFS จะใช้ Memory ตามจำนวน Node ที่อยู่ใน Queue และเวลาตามจำนวน Node / Edge ที่ถูกพิจารณา | ||
|
||
ดังนั้นทั้งคู่จะเป็น $\mathcal{O}(M^2)$ สำหรับข้อนี้ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
ข้อนี้สามารถมองเป็นคำถามว่ามี Binary Search Tree ที่มีตัวเลข $1$ ถึง $N$ กี่แบบ (จากเงื่อนไขที่ 2 และ 3) | ||
|
||
Binary Search Tree คือ Binary Tree ที่ตัวทุกตัวที่อยู่ใน Subtree ของปมลูกซ้ายของแต่ละปมมีค่าน้อยกว่า และที่อยู่ฝั่งขวามีค่ามากกว่า | ||
|
||
### เคส $N \leq 10$ | ||
|
||
กำหนดให้ $F(N)$ เป็นคำตอบว่ามี Binary Search Tree ขนาด $N$ กี่แบบเป็น | ||
|
||
สำหรับ $N=1$ จะได้ $F(1)=1$ เพราะมีปมเดียว | ||
|
||
สังเกตว่าในต้น Binary Search Tree ขนาด $N$ จะเลือกรากได้ $N$ ตัว คือเลือกตัวที่ $1,2,\dots, N$ มาเป็นราก | ||
|
||
เมื่อเลือกรากเป็นตัวเลข $i$ แล้วจะสามารถสร้าง Subtree ในปมลูกซ้ายได้ $F(i-1)$ (เพราะตัวเลข $1,2, \dots, i$ จะต้องอยู่ในฝั่งซ้ายตามเงื่อนไข 3) และในฝั่งขวาจะสร้างได้ $F(N-i)$ วิธี | ||
|
||
ดังนั้นจะได้ว่า $F(N) = \Sigma_{i=1}^N F(i-1)F(N-i)$ | ||
|
||
หากเราคำนวณ $F(N)$ ด้วยวิธี Recursion แบบธรรมดาจะได้ว่าต้องใช้เวลาไม่เกิน $\mathcal{O}(2^N)$ ซึ่งเร็วเพียงพอสำหรับ $N \leq 10$ แต่ช้าไปสำหรับเคสต่อๆ มา | ||
|
||
(ในการคำนวณ $F(N)$ ต้องคำนวณ $F(1), F(2), \dots,F(N-1)$ ดังนั้นจะได้ว่าใช้เวลา $T(N)= \Sigma_{i=1}^N T(i) + \mathcal{O}(N)$ ซึ่งพิสูจน์ด้วย Induction ได้ว่าเป็น $\mathcal{O}(2^N)$) | ||
|
||
### เคส $N \leq 10000$ | ||
|
||
สำหรับเคสต่อมาสามารถใช้ Dynamic Programming บันทึกค่า $F(i)$ ที่ถูกคำนวณไว้แล้ว จะทำให้ไม่ต้องคำนวณค่า $F(i)$ ใหม่ทุกครั้งที่ต้องเรียกใช้ | ||
|
||
ในการคำนวณ $F(i)$ หาก $F(1),F(2),\dots, F(i-1)$ ถูกคำนวณไว้แล้วจะสามารถใช้สูตร $F(N) = \Sigma_{i=1}^N F(i-1)F(N-i)$ เพื่อคำนวณ $F(i)$ ในเวลา $\mathcal{O}(i)$ | ||
|
||
ดังนั้นหากจะคำนวณ $F(N)$ จะสามารถคำนวณ $F(1), F(2), \dots, F(N)$ ตามลำดับซึ่งจะใช้เวลา $\Sigma_{i=1}^N \mathcal{O}(i) = \mathcal{O}(N^2)$ ซึ่งเพียงพอสำหรับข้อนี้ | ||
|
||
### เพิ่มเติม | ||
|
||
จำนวน Binary Search Tree $F(N)$ คือ Catalan Number ตัวที่ $N$ ซึ่งมีสูตรคือ $C_N = \frac{1}{N+1} {2N \choose N} $ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
ข้อนี้ต้องการให้หาจำนวนวิธีที่จะสร้างตาราง $3 \times n$ $(n \leq 2^{30})$โดยวางท่อ 5 แบบดังในรูป เพื่อส่งน้ำจากด้านซ้ายไปยังด้านขวา โดยช่องที่มีทางออกจะต้องต่อกับช่องที่มีทางออกที่เชื่อมกันเท่านั้น | ||
|
||
![](../media/o61_oct_c2_longpipe/tiles.png) | ||
|
||
โดยมีเงื่อนไขว่า | ||
1. ด้านซ้ายสุดมีทางออกด้านซ้าย 1 ทางพอดี | ||
2. ด้านขวาสุดมีทางออกด้านขวา 1 ทางพอดี | ||
3. ห้ามน้ำไหลออกด้านบนด้านล่างตาราง | ||
4. ห้ามมีท่อที่ไม่มีน้ำไหลผ่าน | ||
|
||
ข้อนี้เป็นโจทย์แนว Dynamic Programming โดยใช้ Matrix Exponentiation | ||
|
||
### เคส $ n \leq 500000$ | ||
|
||
สำหรับเคสนี้สามารถใช้ Dynamic Programming แบบธรรมดาโดยยังไม่ต้องใช้ Matrix Exponentiation | ||
|
||
#### State | ||
|
||
ขั้นตอนแรกเราต้องพิจารณา State ที่เป็นไปได้เพื่อจะทำ Dynamic Programming | ||
|
||
State ที่เห็นได้ง่ายที่สุดคือ State ที่น้ำเข้ามาของ row 1 2 และ 3 จากด้านซ้ายเป็น State 1 2 และ 3 ตามลำดับดังในภาพ | ||
|
||
![](../media/o61_oct_c2_longpipe/1.png) | ||
|
||
หากพิจารณากรณีที่มีทางออกด้านซ้ายสองทาง จะเห็นได้ว่าเป็นไปไม่ได้เพราะขัดจะขัดข้อ 1 (เพราะน้ำจะไหลย้อนไปแต่ไม่ย้อนกลับมา) จึงเห็นได้ว่า State ที่เหลือที่เป็นไปได้จะมีทางเข้า 3 ทั้ง 3 row สังเกตว่าในบรรดา State ที่มีน้ำเข้าจากด้านซ้ายทั้ง 3 row นี้ 2 จาก 3 row ที่เข้ามาจะต้องเชื่อมต่อกันอยู่แล้ว เพราะไม่งั้นจะทำให้ขัดกับข้อ 1 อีก (ถ้าไม่เชื่อมกันจะต้องออกด้านซ้ายซึ่งทำให้มีทางเข้าจากด้านซ้ายมากเกิน) เนื่องจากเส้นทางท่อไม่สามารถตัดกันจะได้ว่าทางที่เป็นไปได้คือ row 1 กับ 2 เชื่อมกันแล้ว หรือ row 2 กับ 3 เชื่อมกันแล้ว เราให้ State เหล่านี้เป็น State 4 และ 5 ตามลำดับ | ||
|
||
![](../media/o61_oct_c2_longpipe/2.png) | ||
|
||
#### Transition | ||
|
||
เมื่อเรารู้ State ครบแล้วต้องพิจารณา Transition ระหว่าง State สำหรับ Transition ระหว่าง State 1 2 และ 3 เห็นได้ง่ายจากช่องที่โจทย์ให้ คือ State 1 ไป State 1 หรือ 2 ได้ ส่วน 2 ไป 1 2 หรือ 3 ได้ และ 3 ไป 2 หรือ 3 ได้ ในภาพประกอบแสดงวิธีการ Transition จาก State 2 (ละ Transition ของ State 1 กับ 3 ไว้เพราะคล้ายคลึงกัน) | ||
|
||
![](../media/o61_oct_c2_longpipe/3.png) | ||
|
||
สำหรับ State 4 จะเห็นได้ว่าสามารถมาจาก State 3 ดังในภาพ เมื่อวางท่อเช่นนี้ด้าน row 1 กับ 2 จะเชื่อมกัน และมีทางออกด้านซ้ายทั้ง 3 row | ||
|
||
![](../media/o61_oct_c2_longpipe/4.png) | ||
|
||
เมื่ออยู่ใน State 4 แล้วสามารถอยู่ใน State 4 ต่อ (โดยใช้ท่อตรงต่อหมด) หรือเปลี่ยนไปเป็น State 1 โดยเชื่อม row 2 กับ 3 ดังในภาพ | ||
|
||
![](../media/o61_oct_c2_longpipe/5.png) | ||
|
||
ในทำนองเดียวกับ State 5 จะสามารถมาจาก State 1 และสามารถเปลี่ยนไปเป็น State 5 หรือ 3 | ||
|
||
สรุปคือมี Transition คือ $(1,1), (1,2), (1,5), (2,1), (2,2), (2,3), (3,2), (3,3), (3,4), (4,1), (4,4), (5,3), (5,5)$ | ||
|
||
#### การหาคำตอบ | ||
|
||
กำหนดให้จำนวนวิธีที่มีด้านซ้ายเข้ามาเป็น State $s$ ใน column ที่ $i$ เป็น $dp[i][s]$ | ||
|
||
สังเกตว่าสำหรับ column แรกของตารางจะมีทางเริ่ม 3 State นี้คือน้ำต้องเข้ามาจาก row ที่ 1, 2 หรือ 3 (ตามเงื่อนไข 1) ดังนั้น $dp[1][1] = 1, dp[1][2] = 1, dp[1][3]=1, dp[1][4]=0,dp[1][5]=0$ | ||
|
||
จากนั้นเราสามารถคำนวณจำนวนวิธีที่จะได้แต่ละ State ในแต่ละ column $i$ สำหรับ $1<i< n$ ด้วย Dynamic Programming ตาม Transition ที่ระบุไว้ เนื่องจากมีเพียง 5 State และไม่เกิน $5^2$ Transition ดังนั้นจะใช้เวลา $\mathcal{O}(r^2)$ สำหรับแต่ละ $i$ เมื่อ $r$ คือจำนวน State | ||
|
||
สำหรับ $i=n$ จะต้องคำนวณเพียง $dp[n][1], dp[n][2], dp[n][3]$ ด้วย Transition ดังกล่าว | ||
|
||
ดังนั้นสำหรับทุก $i$ ใช้เวลาไม่เกิน $\mathcal{O}(r^2)$ เมื่อต้องทำ $n$ รอบจะใช้เวลา $\mathcal{O}(nr^2)$ ซึ่งเร็วพอสำหรับ $r=5, n=500000$ | ||
|
||
### เคส $n \leq 2^{30}$ | ||
|
||
สำหรับเคสนี้ต้องใช้ Matrix Exponentiation เพื่อให้ทันเวลา (สำหรับผู้ไม่คุ้นเคยกับ Matrix Exponentiation สามารถอ่านได้จาก https://programming.in.th/tasks/1134/solution) | ||
|
||
สังเกตว่าสามารถเขียน Transition เป็น Matrix $A =\begin{bmatrix} 1 & 1 & 0 & 0 & 1 \\ 1 & 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 1 & 0 \\ 1 & 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 0 & 1 \end{bmatrix} $ | ||
|
||
คำตอบที่ได้จาก Solution ในเคสก่อนหน้านี้จะเป็น $\begin{bmatrix}\ 1 & 1 & 1 & 0 & 0 \end{bmatrix} A^{n} \begin{bmatrix}\ 1 \\\ 1 \\\ 1 \\\ 0 \\\ 0 \end{bmatrix} $ | ||
|
||
ดังนั้นหากเราใช้ Matrix Exponentiation คำนวณ $A^n$ ในเวลา $\mathcal{O}(r^3 \log n)$ จะหาทำให้คำตอบได้ในเวลา $\mathcal{O}(r^3 \log n)$ ซึ่งเพียงพอสำหรับข้อนี้ |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.