ผลต่างระหว่างรุ่นของ "418531 ภาคต้น 2552/โจทยปัญหาการค้นหาด้วยพละกำลังเยี่ยงควายถึก/เฉลยข้อ 6"
Cardcaptor (คุย | มีส่วนร่วม) |
Cardcaptor (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 1 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 104: | แถว 104: | ||
เมื่อพิจารณาโครงสร้างของ loop ข้างบนแล้วก็จะเห็นได้ว่าอัลกอริทึมนี้ใช้เวลาทำงานเท่ากับ <math>O(M^3 N^3 K^2) \,</math> | เมื่อพิจารณาโครงสร้างของ loop ข้างบนแล้วก็จะเห็นได้ว่าอัลกอริทึมนี้ใช้เวลาทำงานเท่ากับ <math>O(M^3 N^3 K^2) \,</math> | ||
+ | |||
+ | == ข้อย่อย 2 == | ||
+ | เราจะเห็นได้ว่าคอขวดของอัลกอริทึมในข้อย่อย 1 คือฟังก์ชัน <code>value</code> ซึ่งใช้เวลาทำงาน <math>O(K^2) \,</math> ฉะนั้นหากเราลดเวลาการทำงานของมันให้เหลือ <math>O(1) \,</math> ได้เราก็จะได้อัลกอริทึมสำหรับแก้ปัญหานี้ที่ทำงานในเวลา <math>O(M^3 N^3) \,</math> | ||
+ | |||
+ | ในข้อย่อยนี้เราจะใช้เทคนิคการทำ precomputation คล้ายๆ กับที่เราเคยใช้ในปัญหาการหาช่วงที่มีผลบวกมากที่สุด ซึ่งเป็นปัญหาที่เราเรียนกันในชั้นเรียน | ||
+ | |||
+ | เพื่อการณ์นี้ เราจะสร้างอะเรย์ <math>S[0 \ldots M, 0 \ldots N] \,</math> โดยที่ | ||
+ | * <math>S[r,c] = 0 \,</math> ถ้า <math>r = 0 \,</math> หรือ <math>c = 0 \,</math> | ||
+ | * <math>S[r,c] = \sum_{i=1}^r \sum_{j=1}^c T[i,j] \,</math> ถ้า <math>r \,</math> และ <math>c \,</math> มากกว่า 0 ทั้งคู่ | ||
+ | |||
+ | เราจะได้ว่า | ||
+ | <table cellpadding="5"> | ||
+ | <tr> | ||
+ | <td align="right"><math>\mathrm{value}(r,c) \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\sum_{i=r}^{r+K-1} \sum_{j=c}^{c+K-1} T[i,j] \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\sum_{i=1}^{r+K-1} \sum_{j=c}^{c+K-1} T[i,j] - \sum_{i=1}^{r-1} \sum_{j=c}^{c+K-1} T[i,j] \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\bigg( \sum_{i=1}^{r+K-1} \sum_{j=1}^{c+K-1} T[i,j] - \sum_{i=1}^{r+K-1} \sum_{j=1}^{c-1} T[i,j] \bigg) - \bigg( \sum_{i=1}^{r-1} \sum_{j=1}^{c+K-1} T[i,j] - \sum_{i=1}^{r-1} \sum_{j=1}^{c-1} T[i,j] \bigg) \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>( S[r+K-1, c+K-1] - S[r+K-1, c-1] ) - ( S[r-1, C+K-1] - S[r-1, c-1] ) \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>S[r+K-1, c+K-1] - S[r+K-1, c-1] - S[r-1, C+K-1] + S[r-1, c-1] \,</math></td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | ดังนั้น หากเราคำนวณอะเรย์ <math>v \,</math> เก็บเอาไว้แล้ว เราสามารถเขียนฟังก์ชัน <code>value</code> ใหม่ซึ่งทำงานภายในเวลา <math>O(1) \,</math> เท่านั้นได้ดังต่อไปนี้ | ||
+ | |||
+ | <geshi lang="c"> | ||
+ | value(r,c) | ||
+ | { | ||
+ | return S[r+K-1,c+K-1] - S[r+K-1,c-1] - S[r-1,C+K-1] + S[r-1,c-1] | ||
+ | } | ||
+ | </geshi> | ||
+ | |||
+ | ปัญหาที่เหลืออยู่คือเราจะสามารถคำนวณค่าในอะเรย์ <math>S \,</math> ได้อย่างรวดเร็วได้อย่างไร? | ||
+ | |||
+ | สังเกตว่า | ||
+ | <table cellpadding="5"> | ||
+ | <tr> | ||
+ | <td align="right"><math>T[r,c] \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\sum_{i=r}^{r} \sum_{j=c}^{c} T[i,j] \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\sum_{i=1}^{r} \sum_{j=c}^{c} T[i,j] - \sum_{i=1}^{r-1} \sum_{j=c}^c T[i,j] \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>\bigg( \sum_{i=1}^{r} \sum_{j=1}^{c} T[i,j] - \sum_{i=1}^{r} \sum_{j=1}^{c} T[i,j] \bigg) - \bigg( \sum_{i=1}^{r-1} \sum_{j=1}^{c} T[i,j] - \sum_{i=1}^{r-1} \sum_{j=1}^{c-1} T[i,j] \bigg) \,</math></td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td align="right"><math> \,</math></td> | ||
+ | <td align="center"><math>= \,</math></td> | ||
+ | <td align="left"><math>S[r,c] - S[r,c-1] - S[r-1,c] - S[r-1,c-1] \,</math></td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | ดังนั้นหากเราทราบค่า <math>S[r,c-1] \,</math>, <math>S[r-1,c] \,</math>, และ <math>S[r-1,c-1] \,</math> แล้ว เราสามารถหาค่า <math>S[r,c] \,</math> ได่้ตามสมการ | ||
+ | : <math>S[r,c] = T[r,c] + S[r-1,c] + S[r,c-1] - S[r-1,c-1] \,</math> | ||
+ | |||
+ | การคำนวณข้างต้นนี้สามารถทำให้เกิดขั้นได้โดยการคำนวณ <math>S[r,c] \,</math> โดยไล่ค่า <math>r \,</math> และ <math>c \,</math> จากน้อยไปหามากดัง pseudocode ข้างล่างนี้ | ||
+ | <geshi lang="c"> | ||
+ | for r = 0 to M do | ||
+ | for c = 0 to N do | ||
+ | { | ||
+ | if r = 0 or c = 0 then | ||
+ | S[r,c] = 0 | ||
+ | else | ||
+ | S[r,c] = T[r,c] + S[r-1,c] + S[r,c-1] - S[r-1,c-1] | ||
+ | } | ||
+ | </geshi> | ||
+ | |||
+ | การคำนวณข้างต้นใช้เวลา <math>O(MN) \,</math> ฉะนั้นอัลกอริทึมสำหรับแก้โจทย์จะใช้เวลาทั้งหมด <math>O(MN + M^3N^3) = O(M^3N^3) \,</math> ตามต้องการ |
รุ่นแก้ไขปัจจุบันเมื่อ 17:54, 28 สิงหาคม 2552
ข้อย่อย 1
วัตถุ
เราสามารถมองว่าวัตถุที่เราต้องการค้นหา คือ "สี่เหลี่ยมขนาด K คูณ K สามอันที่ไม่ซ้อนทับกัน" และเราต้องการสี่เหลี่ยมสามกลุ่มนี้ที่มีผลรวมมากที่สุด
เราสามารถแทนสี่เหลี่ยมแต่ละอันได้ด้วยพิกัดของช่องมุมบนซ้ายของมัน กล่าวคือในตารางขนาด M คูณ N ที่ให้มานี้ เราจะให้หมายเลขของแถวของตารางจากบนลงล่างด้วยหมายเลย 1 ถึง M และหมายเลขคอลัมน์จากซ้ายไปขวาด้วยหมายเลข 1 ถึง N แล้วเราจะสามารถแทนช่องในตารางได้ด้วยคู่ลำดับ โดยที่ และ
ฉะนั้น สี่เหลี่ยมสามรูปสามารถแทนได้ด้วยคู่ลำดับสามคู่ลำดับ แต่คู่ลำดับเหล่านี้สามารถแทนสี่เหลี่ยมที่ซ้อนทับกันได้ (ยกตัวอย่างเช่น ถ้า และ เป็นต้น) ดังนั้นเราต้องการวิธีการเซ็คว่ามีสี่เหลี่ยมสองอันใดๆ ในสี่เหลี่ยมสามอันนี้ซ้อนทับกันหรือไม่ เนื่องจากเราสนใจเฉพาะกลุ่มของสี่เหลี่ยมที่ไม่ซ้อนทับกันเท่านั้น
การเซ็คที่ว่านี้สามารถทำได้โดยเซ็คสี่เหลี่ยมมีละคู่ สำหรับทุกคู่ที่เป็นไปได้ กล่าวคือเราเซ็คว่าสี่เหลี่ยมที่หนึ่งซ้อนทับกับสี่เหลี่ยมที่สองหรือไม่ สองซ้อนทับกับสามหรือไม่ และสามซ้อนทับกับหนึ่งหรือไม่ ถ้ามีคู่ใดคู่หนึ่งทับกันเราก็จะไม่ประมวลผลกลุ่มสี่เหลี่ยมสามอันนั้น
เมื่อกำหนดสี่เหลี่ยมสองอันมาให้ เราสามารถเช็คว่ามันซ้อนทับกันหรือไม่โดยใช้ความจริงดังต่อไปนี้
สี่เหลี่ยมสองสี่เหลี่ยมจะทับกันก็ต่อเมื่อ
- ขอบเขตของมันบนแกนนอนซ้อนทับกัน และ
- ขอบเขตของมันบนแกนตั้งซ้อนทับกัน
ดังนั้นถ้าผู้ใช้กำหนดสี่เหลี่ยม และ เราจะได้ว่า
- ขอบเขตตามแนวแกนนอนของสี่เหลี่ยมแรกคือคอลัมน์
- ขอบเขตตามแนวแกนนอนของสี่เหลี่ยมที่สองคือคอลัมน์
- ขอบเขตตามแนวแกนตั้งของสี่เหลี่ยมแรกคือแถว
- ขอบเขตตามแนวแกนตั้งของสี่เหลี่ยมที่สองคือแถว
ขอบเขตสองขอบเขตใดๆ จะซ้อนทับกันถ้ามีช่องบางช่องที่อยู่ในทั้่งสองขอบเขต กล่าวคือ
- ขอบเขตตามแกนนอนจะซ้อนทับกันเมื่อ
- ขอบเขตตามแกนตั้งจะซ้อนทับกันเมื่อ
ดังนั้นเราสามารถเขียนฟังก์ชัน check_overlap_2
ซึ่งคืนค่า true
เมื่อสี่เหลี่ยมที่ให้สองสี่เหลี่ยมซ้อนทับกัน และคืนค่า false
ถ้าไม่เป็นเช่นนั้นได้ดังต่อไปนี้
<geshi lang="c"> check_overlap_2(r1, c1, r2, r2) {
if (c2 <= c1 + K - 1) and (c1 <= c2 + K - 1) and (r2 <= r1 + K - 1) and (r1 <= r2 + K - 1) then return true else return false
} </geshi>
จากนั้นเราสามารถใช้ check_overlap_2
ในการสร้างฟังก์ชัน check_overlap_3
เพื่อเช็คว่าสี่เหลี่ยมสามอันที่ให้มาซ้อนทับกันหรือไม่ ได้ดังต่อไปนี้
<geshi lang="c"> check_overlap_3(r1, c1, r2, c2, r3, c3) {
if (check_overlap_2(r1, c1, r2, c2) or check_overlap_2(r2, c2, r3, c3) or check_overlap_2(r3, c3, r1, r1)) then return true else return false
} </geshi>
ฉะนั้นในอัลกอริทึมสำหรับแก้โจทย์ปัญหาข้อนี้ เราจึีงสามารถสร้างสี่เหลี่ยมสามอันทั้งหมดที่เป็นไปได้มาก่อน แล้วใช้ check_overlap_3
เพื่อเช็คว่าสี่เหลี่ยมสามอันแต่ละกลุ่มซ้อนทับกันหรือไม่ แล้วเราจึงเลือกประมวลผลเฉพาะกลุ่มของสี่เหลี่ยมที่ไม่ซ้อนทับกันเท่านั้น
ค่าของวัตถุ
เราสามารถเขียนฟังก์ชัน value
เพื่อหาปริมาณนำ้มันสะสมภายในพื้นที่สี่เหลี่ยมหนึ่งผืนได้ดังต่อไปนี้
<geshi lang="c">
value(r, c)
{
result = 0 for i = r to r + K - 1 do for j = c to c + K - 1 do result = result + T[i,j] return result
}
</geshi>
โดยที่ T[,]
เป็นอะเรย์สองมิติที่เก็บตารางขนาด M คูณ N ที่โจทย์กำหนดให้
ต่อไปเราจึงสามารถใช้ value
สร้่างฟังก์ชัน value_3
เพื่อหาปริมาณนำ้มันสะสมในพื้นที่สี่เหลี่ยมสามผืนได้ดังต่อไปนี้
<geshi lang="c"> value_3(r1, c1, r2, c2, r3, c3) {
return value(r1, c1) + value(r2, c2) + value(r2, c3)
} </geshi>
เราเห็นได้อย่างชัดเจนว่าเวลาการทำงานของ value
คือ ฉะนั้นเวลาการทำงานของ value_3
จึงเท่ากับ ด้วย
อัลกอริทึม
เราจะทำการสร้างสี่เหลี่ยมสามรูปขึ้นมาที่ละกลุ่ม เช็คว่าในสี่เหลี่ยมสามรูปนี้มีสองรูปได้ซ้อนทับกันหรือไม่ ถ้าไม่ เราจะหาปริมาณนำ้มันสะสมในพื้นที่สี่เหลี่ยมสามรูป แล้วเอาไปเปรียบเทียบกับค่ามากที่สุดที่เราเคยเจอ และถ้าค่าใหม่ที่ได้มากกว่า เราก็จะจำค่าที่มากที่สุดเอาไว้
<geshi lang="c"> max = 0 for r1 = 1 to M-K+1 do
for c1 = 1 to N-K+1 do for r2 = 1 to M-K+1 do for c2 = 1 to N-K+1 do for r3 = 1 to M-K+1 do for c3 = 1 to N-K+1 do { if not check_overlap3(r1, c1, r2, c2, r3, c3) then { v = value_3(r1, c1, r2, c2, r3, c3) if v > max then max = v } }
</geshi>
เมื่อพิจารณาโครงสร้างของ loop ข้างบนแล้วก็จะเห็นได้ว่าอัลกอริทึมนี้ใช้เวลาทำงานเท่ากับ
ข้อย่อย 2
เราจะเห็นได้ว่าคอขวดของอัลกอริทึมในข้อย่อย 1 คือฟังก์ชัน value
ซึ่งใช้เวลาทำงาน ฉะนั้นหากเราลดเวลาการทำงานของมันให้เหลือ ได้เราก็จะได้อัลกอริทึมสำหรับแก้ปัญหานี้ที่ทำงานในเวลา
ในข้อย่อยนี้เราจะใช้เทคนิคการทำ precomputation คล้ายๆ กับที่เราเคยใช้ในปัญหาการหาช่วงที่มีผลบวกมากที่สุด ซึ่งเป็นปัญหาที่เราเรียนกันในชั้นเรียน
เพื่อการณ์นี้ เราจะสร้างอะเรย์ โดยที่
- ถ้า หรือ
- ถ้า และ มากกว่า 0 ทั้งคู่
เราจะได้ว่า
ดังนั้น หากเราคำนวณอะเรย์ เก็บเอาไว้แล้ว เราสามารถเขียนฟังก์ชัน value
ใหม่ซึ่งทำงานภายในเวลา เท่านั้นได้ดังต่อไปนี้
<geshi lang="c"> value(r,c) {
return S[r+K-1,c+K-1] - S[r+K-1,c-1] - S[r-1,C+K-1] + S[r-1,c-1]
} </geshi>
ปัญหาที่เหลืออยู่คือเราจะสามารถคำนวณค่าในอะเรย์ ได้อย่างรวดเร็วได้อย่างไร?
สังเกตว่า
ดังนั้นหากเราทราบค่า , , และ แล้ว เราสามารถหาค่า ได่้ตามสมการ
การคำนวณข้างต้นนี้สามารถทำให้เกิดขั้นได้โดยการคำนวณ โดยไล่ค่า และ จากน้อยไปหามากดัง pseudocode ข้างล่างนี้ <geshi lang="c"> for r = 0 to M do
for c = 0 to N do { if r = 0 or c = 0 then S[r,c] = 0 else S[r,c] = T[r,c] + S[r-1,c] + S[r,c-1] - S[r-1,c-1] }
</geshi>
การคำนวณข้างต้นใช้เวลา ฉะนั้นอัลกอริทึมสำหรับแก้โจทย์จะใช้เวลาทั้งหมด ตามต้องการ