-
Notifications
You must be signed in to change notification settings - Fork 31
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
34 changed files
with
802 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,96 @@ | ||
โจทย์ข้อนี้มีคำสั่ง $N$ คำสั่ง โดยคำสั่งจะมีสองแบบ | ||
|
||
1. ใส่สินค้ามูลค่า $i$ เข้าไปในเครื่อง | ||
2. ถามว่าสินค้าที่มีมูลค่ามากสุดในเครื่องมีมูลค่าเท่าใดหากมีและเอาออกจากเครื่อง | ||
|
||
โจทย์ข้อนี้แก้ได้ด้วย Priority Queue ซึ่งเป็นโครงสร้างข้อมูลที่มีประสิทธิภาพในการหาค่าสูงสุดของชุดข้อมูลที่อาจมีการเพิ่มเข้าหรือลบออก | ||
|
||
ทั้งนี้ Priority Queue เป็นโครงสร้างข้อมูลที่มีอยู่ใน STL ซึ่งทำให้ข้อนี้แก้ได้ง่ายมากหากใช้ `std::priority_queue` โดยเพียงต้องใช้ฟังก์ชัน `push` เพื่อคำสั่งประเภทแรก และ `top` กับ `pop` สำหรับคำสั่งประเภทสอง | ||
|
||
เนื่องจากโจทย์ข้อนี้เป็นโจทย์ที่ต้องการสอนให้รู้จักการใช้ Priority Queue เฉลยนี้จะอธิบายโครงสร้าง Binary Heap ซึ่งเป็นวิธีที่พื้นฐานที่สุดสำหรับการเขียน Priority Queue (Priority Queue มีความหมายที่กว้างกว่า Binary Heap นอกจาก Binary Heap ยังมีโครงสร้างข้อมูลอื่นที่สามารถใช้เขียน Priority Queue เช่น Fibonacci Heap หรือ Binomial Heap) | ||
|
||
## Binary Heap | ||
|
||
Binary Heap ที่ต้องการในข้อนี้จะต้องรองรับ Operation สองแบบคือ: | ||
1. Push($i$) ใส่ข้อมูลที่มีค่า $i$ เข้าไปใน Heap | ||
2. Pop() return ค่าสูงสุดใน Heap และเอาข้อมูลนั้นออกจาก Heap | ||
โดย Operation ทั้งสองมี Time Complexity $\mathcal{O}(\log{}N)$ และการเก็บ Heap มี Memory Complexity $\mathcal{O}(N)$ (เมื่อ $N$ คือขนาดของ Heap) | ||
|
||
### หลักการทำงานของ Heap | ||
|
||
Heap เป็นโครงสร้างข้อมูลที่เก็บในลักษณะ Complete Binary Tree ซึ่งเป็น Binary Tree ประเภทหนึ่ง | ||
|
||
Binary Tree เป็น Tree ที่แต่ละ Node มีลูกอย่างมาก 2 ตัว | ||
|
||
Complete Binary Tree คือ Binary Tree ที่แต่ละชั้นจะมีจำนวน Node มากสุดที่เป็นไปได้ยกเว้นชั้นสุดท้ายซึ่งจะเก็บ Node ไว้ด้านซ้ายสุดก่อน นั่นคือในชั้นที่ $h$ ของ Binary Tree ที่มีชั้น $H$ เป็นชั้นสุดท้าย จะมี Node $2^h$ ตัวหาก $h < H$ และในชั้น $H$ มีได้ตั้งแต่ $1$ ถึง $2^H$ ตัว | ||
|
||
นอกจากนี้ Heap จะรักษาคุณสมบัติว่าสำหรับ Node $x$ ใดๆ ลูกของ Node $x$ จะมีค่าไม่เกินค่าของ Node $x$ ซึ่งจะทำให้ Node รากที่อยู่สูงสุดใน Heap เป็นค่าสูงสุดในทั้ง Heap | ||
|
||
![](../media/1021/1.png) | ||
|
||
ในการ Implement โครงสร้างข้อมูล Binary Heap โดยทั่วไปจะเก็บเป็น Array จากช่องที่ $1$ ถึง $N$ โดยรากอยู่ที่ $1$ และให้ลูกขวาของ Node ที่ช่อง $x$ ที่ช่อง $2x$ และลูกซ้ายอยู่ที่ $2x+1$ (สังเกตตัวเลขแดงในภาพประกอบซึ่งแทนตำแหน่งของแต่ละ Node ใน Array) | ||
|
||
เนื่องจาก Binary Heap จัดเป็น Complete Binary Tree จะทำให้จำนวนชั้นของ Heap เป็น $\mathcal{O}(\log N)$ เมื่อ $N$ คือจำนวนสมาชิกใน Heap | ||
|
||
#### Push | ||
|
||
สำหรับการ Push ค่า $i$ จะใส่ค่า $i$ เข้าไปที่ตำแหน่งซ้ายสุดของชั้นสุดท้ายที่ยังว่าง หากชั้นสุดท้ายเต็มแล้วจะใส่ในช่องซ้ายสุดของชั้นใหม่ จากนั้นจะสลับค่าที่เพิ่งใส่ไปกับ Node พ่อของมันจนกว่ามันมีค่าไม่เกิน Node พ่อ | ||
|
||
เนื่องจากมีเพียง $\mathcal{O}(\log N)$ ชั้น จะมีการสลับอย่างมาก $\mathcal{O}(\log N)$ ครั้ง ซึ่งหมายความว่า Push มี Time Complexity $\mathcal{O}(\log N)$ | ||
|
||
ตัวอย่างการใส่ 84 เข้าไปใน Heap ตัวอย่างด้านบน | ||
|
||
![](../media/1021/push_1.png) | ||
|
||
84 มีค่ามากกว่า 15 จึงต้องสลับ | ||
|
||
![](../media/1021/push_2.png) | ||
|
||
84 มีค่ามากกว่า 78 จึงสลับอีกรอล | ||
|
||
![](../media/1021/push_3.png) | ||
|
||
84 มีค่าไม่เกิน 90 จึงจบขั้นตอนการ Push | ||
|
||
#### Pop | ||
|
||
สำหรับการ Pop ค่าที่ต้องการ return คือค่าสูงสุดกล่าวคือค่าที่อยู่ที่ราก | ||
|
||
สำหรับการเอาค่านั้นออกจาก Heap จะสลับ Node ในตำแหน่งขวาสุดของชั้นสุดท้ายมาแทนรากเก่า และสลับ Node ที่ถูกสลับขึ้นมากับลูกที่มีค่ามากสุดจน Node นั้นมีค่าไม่ต่ำกว่าลูกทั้งสอง | ||
|
||
Pop มี Time Complexity $\mathcal{O}(\log N)$ เช่นเดียวกับ Push เนื่องจาก Heap มีเพียง $\mathcal{O}(\log N)$ ชั้น | ||
|
||
|
||
ตัวอย่างการ Pop จาก Heap ตัวอย่างด้านบน | ||
|
||
![](../media/1021/push_3.png) | ||
|
||
ค่าที่รากคือ 90 ซึ่งเป็นค่าที่ต้องการ return | ||
|
||
![](../media/1021/pop_1.png) | ||
|
||
สลับ Node ขวาสุดของชั้นสุดท้ายขึ้นมาเป็นรากใหม่ | ||
|
||
![](../media/1021/pop_2.png) | ||
|
||
15 มีค่าน้อยกว่า 90 จึงสลับลงไป | ||
|
||
![](../media/1021/pop_3.png) | ||
|
||
15 มีค่าน้อยกว่า 85 จึงสลับลงไปอีก | ||
|
||
![](../media/1021/pop_4.png) | ||
|
||
15 มีค่าน้อยกว่า 30 จึงสลับลงไปอีก | ||
|
||
![](../media/1021/pop_5.png) | ||
|
||
จบขั้นตอนการ Pop เนื่องจากไม่มีลูกให้สลับลงไปอีก | ||
|
||
## Solution | ||
|
||
เมื่อมี Priority Queue แล้วสำหรับแต่ละคำสั่งใน $N$ คำสั่งที่ได้ เพียวต้อง Push สำหรับ P และ Pop สำหรับ Q | ||
|
||
แต่ละคำสั่งใช้เวลา $\mathcal{O}(\log{}N)$ ไม่ว่าจะเป็น P หรือ Q ดังนั้น Time Complexity ของข้อนี้คือ $\mathcal{O}(N\log{}N)$ | ||
|
||
ภาพประกอบทำใน https://visualgo.net/en |
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,39 @@ | ||
ข้อนี้มีภารกิจ $N \leq 20$ ภารกิจ โดยต้องทำทุกภารกิจเพียงแต่ต้องเลือกลำดับที่จะทำ | ||
|
||
หากทำภารกิจที่ $j$ เป็นลำดับที่ $i$ จะมีโอกาสสำเร็จ $a_{(j,i)}$ โจทย์ถามว่าผลคูณความน่าจะเป็นเหล่านี้ที่เป็นไปได้มากสุดคือเท่าไหร่ | ||
|
||
### แนวคิด | ||
|
||
ข้อนี้เป็นโจทย์ Bitmask Dynamic Programming นั่นคือเป็นโจทย์ Dynamic Programming ที่เก็บ State เป็น Bitmask | ||
|
||
สังเกตว่าเราสามารถเก็บคำตอบของแต่ละ State เป็น $DP[S]$ ซึ่งแทนผลคูณความน่าจะเป็นที่จะสำเร็จโดยที่ภารกิจที่สำเร็จแล้วคือ $S$ เมื่อ State $S=(b_{N}b_{N-1}\dots b_0)_2$ เป็นเลขฐานสองโดยที่ $b_j=1$ ถ้าเราทำภารกิจที่ $j$ แล้ว คำตอบจะเป็น $DP[2^N -1]$ เพราะ $2^N-1 = (11\dots1)_2$ (มี 1 $N$ ตัว) | ||
|
||
เช่นถ้า $S=1010_2$ แสดงว่าทำภารกิจที่ 2 กับ 4 แล้ว | ||
|
||
สังเกตว่าสำหรับ State $S$ จำนวนภารกิจที่ทำไปแล้วจะเท่ากับจำนวน bit ที่เป็น $1$ ให้จำนวนนี้เป็น $i_{S}$ | ||
|
||
สำหรับ $DP[0]$ สามารถตั้งเป็น 100 แทนโอกาส 100% | ||
|
||
ในการคำนวณ $DP[S]$ สังเกตว่าจะต้องมีงานอันภารกิจ $j$ ที่ $b_j=1$ ใน $S$ ดังนั้นสามารถพิจารณาทีละงาน $j$ ดังกล่าวว่าผลคูณที่ดีที่สุดที่เป็นไปได้คือเท่าไหร่ ซึ่งจะได้ว่าเป็น $a_{(j, i_{S})} DP[S - (1<<j)]$ นั่นคือผลคูณของความน่าจะเป็นเมื่อทำงาน $j$ เป็นลำดับที่ $i$ กับผลคูณความน่าจะเป็นที่มากสุดที่เป็นไปได้สำหรับ $S - (1<<j)$ (ซึ่งเป็น State $S$ ก่อนทำภารกิจที่ $j$) | ||
|
||
ดังนั้นสำหรับแต่ละ $S$ หากมีค่า $DP[0], \dots, DP[S-1]$ แล้วจะใช้เวลาคำนวณเพียง $\mathcal{O}(N)$ เมื่อพิจารณาทีละภารกิจ | ||
|
||
ดังนั้นเมื่อต้องพิจารณา $2^N$ State เวลาทั้งหมดที่ใช้คือ $\mathcal{O}(N2^N)$ | ||
|
||
#### ตัวอย่างโค้ด | ||
|
||
```cpp | ||
dp[0] = 100.0; | ||
for (int s = 1; s <= ((1 << n) - 1); s++) { | ||
int i = 0; | ||
for (int j = 0; j < n; j++) | ||
i += (((1 << j) & s) != 0); | ||
|
||
dp[s] = 0; | ||
for (int j = 0; j < n; j++) | ||
if (((1 << j) & s) != 0) | ||
dp[s] = max(dp[s], dp[s ^ (1 << j)] * a[i - 1][j] / 100.0); | ||
} | ||
``` | ||
|
||
ตามคำอธิบายสำหรับแต่ละ $S$ จะนับจำนวนภารกิจที่สำเร็จแล้วใน State $S$ จากนั้นจะไล่ภารกิจที่สำเร็จใน $S$ ว่าควรทำอันไหนเป็นลำดับที่ $i$ |
Oops, something went wrong.