programming.in.th · ข้อ 2027
สามสี่เหลี่ยมที่ไม่ทับกันบนตารางพันห้าร้อยคูณพันห้าร้อย ลากเส้นแบ่งได้เสมอ จึงเหลือแค่หกทรง บทนี้มีบักจริงที่ตอบตัวอย่างในโจทย์ถูก แล้วพังที่ตารางสองคูณสอง
รัฐบาลแบ่งที่ดินแหล่งน้ำมันเป็นตาราง M คูณ N ช่อง
แต่ละช่องมีตัวเลขบอกปริมาณน้ำมันสำรองที่ประเมินไว้ กติกาคือผู้รับเหมาหนึ่งราย
ประมูลได้บล็อกสี่เหลี่ยมจัตุรัสขนาด K คูณ K เพียงบล็อกเดียว
กลุ่มสามบริษัทที่ฮั้วกันจึงอยากเลือกสามบล็อกที่ไม่ทับกันเลย ให้ผลรวมมากที่สุด งานของเราคือบอกผลรวมนั้น
อินพุต / ขอบเขต / เอาต์พุต
M, N และ K
จากนั้นคือตาราง M บรรทัด บรรทัดละ N จำนวนเต็มไม่ติดลบ
K ≤ M, K ≤ N,
M, N ≤ 1,500 ค่าในแต่ละช่องไม่เกิน 500 และรับประกันว่าวางบล็อกที่ไม่ทับกันได้อย่างน้อยสามบล็อก
เวลา 1.5 วินาที หน่วยความจำ 128 เมกะไบต์
M และ N ไม่เกิน 12| Input | Output |
|---|---|
| 9 9 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 8 8 8 8 1 1 1 1 8 8 8 8 8 1 1 1 1 8 8 8 8 8 1 1 1 1 1 1 1 8 8 8 1 1 1 1 1 1 1 1 8 8 8 1 1 1 1 1 1 9 9 9 1 1 1 1 1 1 9 9 9 | 208 |
โจทย์เล่าไว้เองว่าตารางเดียวกันนี้ ถ้า K เท่ากับ 2 จะได้ 100
ส่วนที่ K เท่ากับ 3 จะได้ 208 ตัวเลขสองตัวนี้หน้าเว็บคำนวณเองตอนสร้างหน้า
และตรงกับที่โจทย์บอกทั้งคู่
ใบ้
จำนวนตำแหน่งที่วางบล็อกได้มีราว M คูณ N ตำแหน่ง
ที่ 1,500 คูณ 1,500 คือสองล้านกว่าตำแหน่ง ถ้าลองทุกสามตำแหน่ง
มันคือเลขยกกำลังสาม ซึ่งไม่ต้องคิดต่อเลย
ลองวาดสี่เหลี่ยมสามอันที่ไม่ทับกันบนกระดาษดูหลาย ๆ แบบ แล้วถามว่า มีเส้นตรงเส้นหนึ่งที่ลากผ่านทั้งกระดานโดยไม่ตัดบล็อกไหนเลยเสมอไหม
บล็อกขนาดหนึ่งช่องคือกรณีที่ง่ายที่สุด กดสามช่องที่คิดว่ารวมกันได้มากที่สุด
กดที่ช่องเพื่อวางมุมบนซ้ายของบล็อก วางได้สามบล็อก กดซ้ำที่เดิมเพื่อเอาออก
วางแล้ว 0 จาก 3 บล็อก
บล็อกกินพื้นที่ลงล่างและไปขวาจากช่องที่กด และห้ามทับกันแม้แต่ช่องเดียว
ตารางแรกจงใจวางของดีไว้ที่มุมทั้งสี่ ลองสังเกตว่าเวลาเลือกสามอันที่ดีที่สุดแล้ว มันยังเหลือช่องว่างให้ลากเส้นตรงผ่านกลางได้เสมอ ซึ่งเป็นข้อสังเกตที่ทั้งข้อยืนอยู่บนมัน
เรื่องจริงที่เกิดขึ้นตามลำดับ
ผมเห็นเรื่องหกทรงค่อนข้างเร็ว เพราะมันคือคำถามเดียวว่าจะแบ่งกระดานยังไงให้แต่ละส่วนมีบล็อกเดียว แต่ตอนแปลงหกทรงเป็นโค้ด ผมเขียนขอบเขตของเส้นตัดผิดไปสองทรง คือทรงที่ครึ่งหนึ่งของกระดาน ต้องวางสองบล็อกเรียงกัน ผมใส่ว่าครึ่งนั้นต้องกว้างอย่างน้อย 2K
ซึ่งผิด เพราะสองบล็อกที่วางเรียงกันในแนวนอน ต้องการความสูงแค่ K
ส่วนที่ต้องการ 2K คือความกว้าง ผมสลับสองแกนนี้กันในหัว
รุ่นที่ผิดนั้นตอบตัวอย่าง 9 คูณ 9 ในโจทย์ได้ 208 ถูกเป๊ะ ถ้าผมเชื่อตัวอย่าง
ผมก็ส่งไปแล้ว สิ่งที่จับได้คือตัวสุ่มเทียบกับตัวตรวจ ซึ่งพังตั้งแต่เคสที่ 51
แล้วผมกวาดเคสเล็กแบบไล่ให้ครบเพื่อหาเคสที่เล็กที่สุด ได้ตาราง 2 คูณ 2 ที่ K เท่ากับ 1
บทเรียนที่ยกไปข้ออื่นได้คือ เวลาโค้ดมีเงื่อนไขขอบแบบ 2K หรือ K
ให้กลับไปถามเป็นภาษาคนว่าของสองชิ้นวางเรียงกันแล้วกินพื้นที่ทางไหน
เพราะความผิดพลาดแบบนี้ไม่โผล่ในตัวอย่างของโจทย์ ซึ่งมักใหญ่พอที่ทุกเงื่อนไขจะเป็นจริงอยู่แล้ว
ข้อสังเกตที่ทั้งข้อยืนอยู่บนมันคือ ถ้ามีสี่เหลี่ยมสามอันที่ไม่ทับกันบนกระดาน เราจะลากเส้นตรงเต็มความกว้างหรือเต็มความสูงได้เสมอหนึ่งเส้น โดยไม่ตัดสี่เหลี่ยมไหนเลย เส้นนั้นแบ่งกระดานเป็นสองฝั่ง ฝั่งหนึ่งมีหนึ่งบล็อก อีกฝั่งมีสอง แล้วฝั่งที่มีสองก็ลากเส้นแบ่งได้อีกทีด้วยเหตุผลเดียวกัน
พอไล่ให้ครบว่าเส้นแรกเป็นแนวนอนหรือแนวตั้ง และเส้นที่สองอยู่ฝั่งไหนและเป็นแนวไหน ก็ได้ทั้งหมดหกทรง ตามภาพ
พอรู้ว่ามีแค่หกทรง คำถามก็เปลี่ยนจากเลือกสามบล็อก เป็นเลือกเส้นตัด แล้วถามหาบล็อกที่ดีที่สุดของแต่ละส่วน
ก่อนอื่นต้องรู้ผลรวมของบล็อกทุกตำแหน่ง ซึ่งได้จากผลรวมสะสมสองมิติ
คือตารางที่ช่อง (i, j) เก็บผลรวมของสี่เหลี่ยมตั้งแต่มุมบนซ้ายถึงช่องนั้น
แล้วผลรวมของสี่เหลี่ยมใด ๆ ก็หาได้ด้วยการบวกลบสี่ตัว
จากนั้นเราต้องตอบคำถามแบบ "บล็อกที่ดีที่สุดที่อยู่ในสี่เหลี่ยมมุมบนซ้ายทั้งก้อน คือเท่าไร" ซึ่งสร้างเป็นตารางไว้ล่วงหน้าได้ด้วยการไล่ครั้งเดียว โดยแต่ละช่องรับค่าที่ดีที่สุดมาจากช่องบนกับช่องซ้าย
// ตารางมุม ค่าที่ดีที่สุดของบล็อกที่อยู่ในสี่เหลี่ยมมุมบนซ้ายทั้งก้อน
// สร้างด้วยการไล่ครั้งเดียว โดยรับค่ามาจากช่องบนกับช่องซ้าย
F1(i, j) = max(BK(i, j), max(F1(i-1, j), F1(i, j-1))); ทำแบบเดียวกันทั้งสี่มุม คือบนซ้าย บนขวา ล่างซ้าย ล่างขวา แล้วทุกส่วนที่เกิดจากการตัดในหกทรง ก็เป็นสี่เหลี่ยมที่ติดมุมใดมุมหนึ่งของกระดานเสมอ จึงถามได้ในเวลาคงที่ทั้งหมด
โน้ต · ทรงแถบสามแถบไม่ต้องใช้ตารางมุม
สำหรับสามแถบ เราเดินตามแถบกลางทีละตำแหน่งก็พอ เพราะแถบบนคือทุกอย่างเหนือมัน
และแถบล่างคือทุกอย่างใต้มัน ซึ่งเป็นค่าสูงสุดสะสมจากหัวและจากท้ายของอาเรย์มิติเดียว
ทรงนี้จึงใช้เวลาแค่ M ไม่ใช่ M คูณ N
เดินทีละทรง บนตาราง 9 x 9 ที่ K เท่ากับ 3
| ทรงที่ | รูปแบบการแบ่ง | ค่าที่ได้ |
|---|---|---|
| 1 | แถบแนวนอนสามแถบ | 173 |
| 2 | แถบแนวตั้งสามแถบ | 201 |
| 3 | ตัดขวางหนึ่งเส้น ครึ่งล่างตัดตั้ง | 173 |
| 4 | ตัดขวางหนึ่งเส้น ครึ่งบนตัดตั้ง | 208 |
| 5 | ตัดตั้งหนึ่งเส้น ครึ่งขวาตัดขวาง | 208 |
| 6 | ตัดตั้งหนึ่งเส้น ครึ่งซ้ายตัดขวาง | 173 |
คำตอบคือ 208 ซึ่งมาจากสามบล็อกที่มุมบนซ้ายอยู่ที่แถวคอลัมน์ (3, 2), (4, 5), (7, 7) นับจากหนึ่ง ตำแหน่งชุดนี้มาจากตัวตรวจที่ลองทุกสามบล็อก ไม่ได้มาจากท่าหกทรง
เวลาที่วัดได้บนเครื่องผม ตาราง 1,500 คูณ 1,500 ที่ K เท่ากับ 50
ใช้เวลา 0.53 วินาที จากลิมิต 1.5 วินาที
ท่าของข้อนี้คือเปลี่ยนคำถามจากเลือกของ เป็นเลือกเส้นแบ่ง เวลาโจทย์ให้เลือกสี่เหลี่ยมหลายอันที่ไม่ทับกัน ให้ถามก่อนว่ามันมีเส้นแบ่งที่ต้องมีอยู่เสมอไหม ถ้ามี จำนวนทรงของการวางจะเป็นเลขคงที่เล็ก ๆ แล้วปัญหาจะยุบเหลือการไล่ตำแหน่งเส้น
ทบทวนพื้นฐาน · ผลรวมสะสมสองมิติ
ตารางผลรวมสะสมสร้างด้วยสูตร P(i,j) = a(i,j) + P(i-1,j) + P(i,j-1) - P(i-1,j-1)
ที่ต้องลบตัวสุดท้ายเพราะสี่เหลี่ยมสองผืนที่บวกเข้ามาซ้อนทับกันอยู่หนึ่งผืน
พอมีตารางนี้แล้ว ผลรวมของสี่เหลี่ยมใด ๆ คิดได้ด้วยการบวกลบสี่ตัว
เรื่องนี้มีบทปูพื้นอยู่แล้วที่ ผลรวมสะสมกับต้นไม้เฟนวิก ซึ่งอธิบายทั้งกรณีมิติเดียวและการต่อยอดไปเป็นโครงสร้างที่แก้ค่าระหว่างทางได้
| ทาง | ขอบเขตที่มันไหว | งานคร่าว ๆ |
|---|---|---|
| ลองทุกสามบล็อกที่ไม่ทับกัน | ตารางไม่เกินราว 12 คูณ 12 | จำนวนตำแหน่งยกกำลังสาม |
| ผลรวมสะสม บวกตารางมุมสี่ทิศ บวกหกทรง | ตารางถึง 1,500 คูณ 1,500 | ราว 13.5 ล้านครั้ง |
สี่เหลี่ยมสามอันที่ไม่ทับกัน ลากเส้นแบ่งเต็มกระดานได้เสมอ การเลือกสามบล็อกจึงกลายเป็นการเลือกเส้นตัด ซึ่งมีแค่หกทรง และแต่ละส่วนที่ได้ก็ถามค่าที่ดีที่สุดจากตารางมุมได้ในเวลาคงที่
ในหน้านี้