Skip to content

Commit

Permalink
Merge branch 'master' into add_1134
Browse files Browse the repository at this point in the history
  • Loading branch information
iammarkps authored Oct 29, 2023
2 parents 9832fe8 + 82517dd commit 494dc33
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 0 deletions.
121 changes: 121 additions & 0 deletions md/1061.md
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)$ สำหรับข้อนี้
31 changes: 31 additions & 0 deletions md/codecube_031.md
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} $
67 changes: 67 additions & 0 deletions md/o61_oct_c2_longpipe.md
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)$ ซึ่งเพียงพอสำหรับข้อนี้
Binary file added media/1061/0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/1061/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/1061/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/1061/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/o61_oct_c2_longpipe/tiles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 494dc33

Please sign in to comment.