programming.in.th · ข้อ 2013
กลับไปถามที่มุมล่างซ้ายของฐาน แล้วก้อนแต่ละก้อนจะกลายเป็นสี่เหลี่ยมบวกค่าที่ไล่ทีละคอลัมน์ได้ ส่วนชุดงบศูนย์ใช้ช่วงคอลัมน์ที่ยืดหดเองแทนการเดาขนาด
พื้นที่สำรวจเป็นตารางขนาด M × N ช่อง ฐานปิรามิดต้องเป็นจัตุรัสที่ด้านขนานกับเส้นตาราง
บนพื้นที่มีสิ่งกีดขวาง P ก้อน แต่ละก้อนเป็นสี่เหลี่ยมในตาราง (ทับซ้อนกันได้)
การทำลายก้อนที่ i เสียค่าใช้จ่าย C และต้องทำลายทั้งก้อน จะทุบแค่ส่วนที่ขวางไม่ได้
เรามีงบ B งานคือหาด้านที่ยาวที่สุดของฐานที่วางได้ โดยค่าทำลายก้อนที่ทับฐานรวมแล้วไม่เกินงบ
ถ้าวางไม่ได้เลยให้ตอบ 0
อินพุต / ขอบเขต / เอาต์พุต
M N บรรทัดที่ 2 คือ B บรรทัดที่ 3 คือ P
จากนั้นอีก P บรรทัด บรรทัดละ X1 Y1 X2 Y2 C ตามลำดับนี้ คือช่องล่างซ้ายของก้อน ช่องบนขวาของก้อน และค่ารื้อ
(1, 1) ช่องบนขวาสุดคือ (M, N)
ก้อนคลุมทุกช่องตั้งแต่ (X1, Y1) ถึง (X2, Y2) รวมช่องที่ขอบด้วย
1 ≤ M, N ≤ 1,000,000, 1 ≤ C ≤ 7,000,
1 ≤ X1 ≤ X2 ≤ M, 1 ≤ Y1 ≤ Y2 ≤ N เวลา 5 วินาที หน่วยความจำ 256 เมกะไบต์
B = 0 และ 1 ≤ P ≤ 1,000
ชุดที่สอง 35 คะแนน 0 < B ≤ 2,000,000,000 และ 1 ≤ P ≤ 30,000
ชุดที่สาม 30 คะแนน B = 0 และ 1 ≤ P ≤ 400,000 | Input | Output |
|---|---|
| 6 9 42 5 4 1 6 3 12 3 6 5 6 9 1 3 3 8 24 3 8 6 9 21 5 1 6 2 20 | 4 |
| 13 5 0 8 8 4 10 4 1 4 3 4 4 1 10 2 12 2 2 8 2 8 4 3 2 4 6 4 5 10 3 10 4 8 12 3 12 4 13 2 2 4 2 21 | 3 |
อ่านตัวอย่างแรก: ตารางกว้าง 6 ช่องตามแกน x สูง 9 ช่องตามแกน y งบ 42 มี 5 ก้อน
บรรทัด 4 1 6 3 12 คือก้อนที่คลุมช่อง (4, 1) ถึง (6, 3) ค่ารื้อ 12
ใบ้
ตารางใหญ่ได้ถึงล้านคูณล้าน เราแตะทุกช่องไม่ได้ แต่สิ่งกีดขวางมีแค่ไม่กี่แสนก้อน ข้อมูลจริงที่มีอยู่น้อยกว่าตารางมหาศาล
ลองถามว่า ถ้ารู้ว่าจัตุรัสด้าน s วางได้ แล้วจัตุรัสด้าน s − 1 วางได้ไหม
และถ้าคิดกลับด้าน มองว่ามุมล่างซ้ายของจัตุรัสวางได้ที่ไหนบ้าง
สิ่งกีดขวางหนึ่งก้อนจะห้ามมุมล่างซ้ายไว้ที่บริเวณรูปอะไร
ตารางเดียวกับตัวอย่างแรกของโจทย์ งบ 42
คลิกช่องเพื่อวางมุมล่างซ้ายของฐาน แล้วปรับด้านยาว ตัวเลขในช่องคือค่ารื้อรวมของก้อนที่ทับช่องนั้น
ต้องจ่าย 0 จากงบ 0 · ใหญ่ที่สุดที่วางได้แล้ว 0
ก้อนหนึ่งก้อนคิดเงินครั้งเดียว ไม่ว่าฐานจะทับมันกี่ช่องก็ตาม
ลองเลื่อนฐานทีละช่องแล้วดูว่าค่าใช้จ่ายเปลี่ยนตอนไหน ค่าจะนิ่งอยู่จนกว่าขอบของฐานจะข้ามขอบของก้อนสักก้อน
เริ่มจากวิธีที่ตรงกับโจทย์ที่สุด ไล่ทุกด้านยาว s ทุกมุมล่างซ้าย (x, y)
แล้วรวมค่ารื้อของก้อนที่ทับฐาน ก้อนไหนทับก็คิดเงินครั้งเดียว
ก้อนทับฐานเมื่อทั้งสองเหลื่อมกันทั้งแนวนอนและแนวตั้ง ภาพข้างล่างคือผลของวิธีนี้บนตัวอย่างแรก
วิธีนี้ถูกแน่นอน และหน้านี้ใช้มันเป็นตัวตรวจของทุกวิธีที่ตามมา ปัญหาอยู่ที่ปริมาณงาน
ลองคูณที่ขอบเขตเต็ม
s มีได้ตั้งแต่ 1 ถึง 10⁶s มีมุมให้ลอง (M − s + 1)(N − s + 1) มุม รวมทุกด้านเท่ากับ 1² + 2² + … + M² ≈ M³/3 ≈ 3.3 × 10¹⁷ มุมเครื่องทั่วไปทำงานง่าย ๆ ได้ราว 10⁸ ครั้งต่อวินาที (กะคร่าว ๆ) ใน 5 วินาทีได้ราว 5 × 10⁸ ครั้ง วิธีนี้ช้ากว่างบราว 2.7 × 10¹⁴ เท่า
งานก้อนนี้คือจำนวนขนาด คูณจำนวนมุม คูณจำนวนก้อน ตอนต่อ ๆ ไปลดทีละตัว และชุดทดสอบย่อยแต่ละชุดเปิดทางให้ลดคนละแบบ
| ชุด | คะแนน | งบ | จำนวนก้อน |
|---|---|---|---|
| 1 | 35 | B = 0 | 1 ≤ P ≤ 1,000 |
| 2 | 35 | 0 < B ≤ 2,000,000,000 | 1 ≤ P ≤ 30,000 |
| 3 | 30 | B = 0 | 1 ≤ P ≤ 400,000 |
ตัวที่ลดง่ายที่สุดคือจำนวนขนาด ลองดูว่าถ้ารู้แล้วว่าด้าน s วางได้ เราบอกอะไรเกี่ยวกับด้านที่สั้นกว่าได้บ้าง
เอาฐานด้าน s − 1 ไปวางที่มุมเดียวกับฐานด้าน s ตัวเล็กอยู่ข้างในตัวใหญ่ทั้งตัว
ก้อนไหนทับตัวเล็กก็ทับตัวใหญ่ด้วย ค่ารื้อของตัวเล็กเลยไม่เกินตัวใหญ่
ข้อสังเกตของตอนนี้
ถ้าด้าน s วางได้ ทุกด้านที่สั้นกว่าวางได้หมด คำตอบของคำถาม "วางได้ไหม" เรียงเป็น ได้ ได้ ... ได้ แล้วไม่ได้ไปตลอด
คำตอบที่เรียงแบบนี้ใช้การค้นหาคำตอบแบบไบนารี (binary search on the answer อ่านว่า "ไบนารีเสิร์ช") ได้ คือถามขนาดตรงกลางของช่วงที่ยังไม่รู้ ถ้าได้ก็ทิ้งครึ่งล่าง ถ้าไม่ได้ก็ทิ้งครึ่งบน
ช่วง 0 ถึง 1,000,000 หดเหลือค่าเดียวใน 20 รอบ เพราะหารครึ่ง 20 ครั้งก็เหลือไม่ถึงหนึ่ง ท่านี้มีบทปูพื้นแยกไว้ที่เซกเมนต์ทรีกับการค้นหาคำตอบแบบไบนารี
| ด้านยาว | ค่าใช้จ่ายต่ำสุด | มุมล่างซ้ายที่ถูกที่สุด | อยู่ในงบไหม |
|---|---|---|---|
| 1 | 0 | (1, 1) | ได้ |
| 2 | 0 | (1, 1) | ได้ |
| 3 | 9 | (4, 4) | ได้ |
| 4 | 33 | (1, 4) | ได้ |
| 5 | 45 | (1, 3) | ไม่ได้ |
| 6 | 54 | (1, 4) | ไม่ได้ |
// s ใหญ่ขึ้น ค่าใช้จ่ายก็ไม่มีทางถูกลง เพราะจัตุรัสเล็กที่ซ้อนอยู่ข้างในย่อมชนก้อนไม่มากกว่า
// เงื่อนไข "มีที่วางได้" จึงเป็นจริงเรียงติดกันแล้วเท็จตลอด ค้นหาแบบไบนารีได้
ll lo = 0, hi = min(M, N);
while (lo < hi) {
ll md = (lo + hi + 1) / 2;
if (ok(md)) lo = md; else hi = md - 1;
} ที่มาของแนวคิดนี้
ไบนารีเหลือ 20 รอบก็จริง แต่ในแต่ละรอบยังต้องไล่มุมนับล้านล้าน ตารางใหญ่ขนาดนี้แตะทุกช่องไม่ได้ ในขณะที่ก้อนมีแค่หลักแสน อัลกอริทึมต้องเดินตามก้อนแทน
ผมเลยกลับคำถาม จากเดิมที่ถามว่า "ฐานที่มุมนี้ทับก้อนไหน" เป็น "ก้อนนี้ทับฐานที่มุมไหนบ้าง"
ตอนนี้ใช้ตารางเล็กชุดหนึ่ง (ชุดเดียวกับแท็บที่ 4 ของเกม) จะได้เห็นทุกตัวเลขในหน้าเดียว ตารางกว้าง 9 สูง 5 งบ 3 มี 4 ก้อน
ตรึงด้าน s = 3 มุมล่างซ้ายมี x ได้ 1 ถึง M − s + 1 = 7
และ y ได้ 1 ถึง N − s + 1 = 3 ถ้ามุมอยู่เลยกว่านี้ ฐานจะยื่นออกนอกตาราง
เริ่มจากก้อน C ในแนวนอน เลื่อนฐานจากซ้ายไปขวาแล้วดูว่าที่มุมไหนเริ่มทับ และที่มุมไหนเลิกทับ
ฐานที่มุม x กินคอลัมน์ x ถึง x + s − 1 มันเริ่มทับเมื่อขอบขวา x + s − 1 แตะคอลัมน์แรกของก้อน
คือ x = X1 − s + 1 = 5 − 3 + 1 = 3 และทับไปจนขอบซ้าย x ยังอยู่ที่คอลัมน์สุดท้ายของก้อน คือ x = X2 = 6
แนวตั้งของก้อน C แล้วนับให้ครบ
y = 4 − 3 + 1 = 2 และทับไปถึง y = 5y สูงสุดแค่ 3 เลยตัดเหลือ y = 2 ถึง 3มุมที่ทับก้อน C คือกรอบ 4 × 2 = 8 มุม ไล่ตรวจทุกมุมด้วยวิธีของตอนที่ 1 ก็เจอ 8 มุมชุดเดียวกัน
สูตรทั่วไป
x อยู่ระหว่าง X1 − s + 1 ถึง X2y อยู่ระหว่าง Y1 − s + 1 ถึง Y2M − s + 1 และ 1 ถึง N − s + 1คำถามในคำใบ้ที่ว่าก้อนหนึ่งก้อนห้ามมุมล่างซ้ายไว้เป็นรูปอะไร คำตอบคือสี่เหลี่ยมผืนเดียว เพราะแต่ละแกนได้ช่วงต่อเนื่องหนึ่งช่วง
นึกภาพ
คิดว่าก้อนแต่ละก้อนเป็นด่านเก็บเงินที่ปักป้ายคลุมพื้นที่สี่เหลี่ยมบนแผนที่ของมุม ยืนอยู่ที่มุมไหน ก็จ่ายให้ทุกด่านที่ป้ายคลุมจุดนั้น ด่านละครั้ง
คำถาม "ด้าน s วางได้ไหม" เลยเท่ากับถามว่า บนแผนที่ของมุม มีจุดไหนที่ผลรวมของสี่เหลี่ยมที่คลุมอยู่ไม่เกินงบ
งบเป็นศูนย์แปลว่าห้ามชนก้อนไหนเลย ผลรวมไม่มีความหมายแล้ว เหลือแค่ถามว่าบนแผนที่ของมุม มีจุดที่ไม่มีสี่เหลี่ยมผืนไหนคลุมหรือเปล่า
สมมติเจอมุมว่างสักจุด ลองเลื่อนมันไปทางซ้ายทีละคอลัมน์ ตราบที่ยังว่างก็เลื่อนต่อได้ มันจะหยุดได้สองแบบ
x = 1X2 คอลัมน์ว่างแรกถัดออกมาคือ x = X2 + 1
ถ้ามีมุมว่าง ก็ต้องมีมุมว่างที่ x เป็น 1 หรือ X2 + 1 ของสักก้อน
x ที่ต้องลองมีไม่เกิน P + 1 ค่า ค่าที่เกิน M − s + 1 ทิ้งได้ เพราะฐานที่มุมนั้นล้นตาราง
y ห้ามของก้อนที่คลุมคอลัมน์ x โดยตัดปลายบนไม่ให้เกิน N − s + 1y = 1 ถ้าช่วงถัดไปเริ่มเลยตำแหน่งปัจจุบัน แปลว่าเจอช่องว่างN − s + 1 ก็มีช่องว่างเหนือช่วงสุดท้ายx ถัดไป
ต่อหนึ่งรอบของไบนารีมี x ไม่เกิน P + 1 ค่า แต่ละค่าเช็ก P ก้อนแล้วเรียงช่วงอีกราว P log₂ P ครั้ง
ชุดที่ 1 รวมได้ราว 2.2 × 10⁸ ใช้เวลา 0.07 วินาที แต่ที่ P = 400,000 พุ่งไปราว 6.4 × 10¹³
ชุดที่ 2 ใช้ท่านี้ไม่ได้ตั้งแต่ต้น งบมากกว่าศูนย์ต้องหาค่าต่ำสุดของผลรวม ซึ่งการหาช่องว่างตอบให้ไม่ได้
ที่มาของแนวคิดนี้
พอต้องหาค่าต่ำสุดของผลรวม ผมลองตรึงคอลัมน์ x ของแผนที่มุมไว้คอลัมน์เดียว
สี่เหลี่ยมที่คลุมคอลัมน์นั้นแต่ละผืน คือการบวกค่าลงช่วงหนึ่งของ y เหลือแค่ทำให้คอลัมน์เลื่อนได้
การกวาดเส้น (sweep line อ่านว่า "สวีปไลน์") คือเดินเส้นตั้งจากซ้ายไปขวาบนแผนที่ของมุม แล้วเก็บค่าของคอลัมน์ที่เส้นอยู่ไว้ในโครงสร้างข้อมูลตัวเดียว สี่เหลี่ยมหนึ่งผืนสร้างเหตุการณ์สองครั้ง
y ของมันM − s + 1 ไม่ต้องสร้างเหตุการณ์ออก เพราะเส้นไปไม่ถึง
ระหว่างสองเหตุการณ์ที่ติดกัน ค่าในคอลัมน์ไม่เปลี่ยน ถามค่าต่ำสุดแค่ที่ x ที่มีเหตุการณ์ก็พอ
ยกเว้น x = 1 ที่ต้องถามเสมอแม้ไม่มีเหตุการณ์ เพราะคอลัมน์ซ้ายสุดอาจว่างทั้งคอลัมน์ โค้ดเริ่มที่ cur = 1 แล้วกระโดดไปเหตุการณ์ถัดไปทีละครั้ง
ลองเดินด้าน 3 งบ 3 บนตารางของตอนที่ 3 ตัวเลขในคอลัมน์ที่ไฮไลต์คือค่ารื้อรวมของแต่ละ y ณ ตอนนั้น
ส่วนว่าเก็บค่าพวกนี้ไว้ยังไง ตอนที่ 6 จะเปิดดู
แผนที่ของมุมเมื่อ s = 3 กว้าง 7 สูง 3 งบ 3
| ขั้น | x | เกิดอะไร | ค่าที่ y = 1 ถึง 3 | น้อยสุด |
|---|---|---|---|---|
| 1 | 1 | A เข้า +5 | 5, 5, 5 | |
| 2 | 1 | ถามค่าน้อยสุด | 5, 5, 5 | 5 |
| 3 | 2 | B เข้า +3 | 8, 8, 5 | |
| 4 | 2 | ถามค่าน้อยสุด | 8, 8, 5 | 5 |
| 5 | 3 | C เข้า +4 | 8, 12, 9 | |
| 6 | 3 | A ออก −5 | 3, 7, 4 | |
| 7 | 3 | ถามค่าน้อยสุด | 3, 7, 4 | 3 |
ด้าน 3 จบที่ x = 3 เพราะเจอมุมที่ค่ารื้อ 3 ไม่เกินงบ
ส่วนด้าน 4 กวาดจนหมดเหตุการณ์ ค่าต่ำสุดที่เจอคือ 7 มากกว่างบ 3 คำตอบของตารางนี้คือ 3
เครื่องเดินของตอนที่ 5 ต้องการสองคำสั่ง คือบวกค่าทั้งช่วง y และถามค่าต่ำสุดของทั้งคอลัมน์
โครงสร้างที่ทำทั้งสองอย่างได้เร็วคือเซกเมนต์ทรี (segment tree อ่านว่า "เซกเมนต์ทรี")
มันเป็นต้นไม้ที่แต่ละปมดูแลช่วงหนึ่งของ y ปมบนสุดเรียกว่ารากดูแลทั้งคอลัมน์ ปมล่างสุดเรียกว่าใบดูแลทีละแถว
ปม i มีลูก 2i กับ 2i + 1
ต้นไม้ของตัวอย่างมี 4 ใบ คือใบละแถวสำหรับ y = 1 ถึง 3 และใบส่วนเกินอีก 1 ใบที่เติมให้จำนวนใบเป็นกำลังสอง
ใบส่วนเกินตั้งเป็นอนันต์ (∞) ไว้ จะได้ไม่ชนะตอนหาค่าต่ำสุด
การบวกทั้งช่วงไม่ได้ไล่บวกทีละใบ มันวางป้ายไว้ที่ปมที่ช่วงของปมอยู่ในช่วงที่บวกทั้งหมด เช่นเหตุการณ์แรก A เข้า +5 ที่ y 1 ถึง 3 ป้ายไปอยู่ที่ปม 6 กับ 2 (y 3 และ y 1 ถึง 2)
| เหตุการณ์ | ปมที่ได้ป้าย |
|---|---|
| A เข้า +5 ที่ y 1 ถึง 3 | ปม 6 (y 3), ปม 2 (y 1 ถึง 2) |
| B เข้า +3 ที่ y 1 ถึง 2 | ปม 2 (y 1 ถึง 2) |
| C เข้า +4 ที่ y 2 ถึง 3 | ปม 5 (y 2), ปม 6 (y 3) |
| A ออก −5 ที่ y 1 ถึง 3 | ปม 6 (y 3), ปม 2 (y 1 ถึง 2) |
สิ่งที่แต่ละปมจำ
lz[i]: ค่าที่บวกให้ทั้งช่วงของปมนี้แล้วค้างไว้ที่นี่ เรียกว่าป้ายค้าง (lazy)mn[i] = min(mn[2i], mn[2i+1]) + lz[i]: ค่าต่ำสุดของช่วงนี้ คือค่าต่ำสุดของลูก บวกป้ายที่ค้างไว้กับทั้งช่วง
ที่ปม 2: mn[2] = min(0, 4) + 3 = 3
ค่าจริงของแถวหนึ่งคือค่าที่ใบบวกป้ายทุกอันบนทางขึ้นราก เช่นแถว y = 1 ได้ 0 + 3 + 0 = 3
ตรงกับคอลัมน์ [3, 7, 4] ของขั้นสุดท้ายในเครื่องเดิน
คำถามมีแค่ที่ราก และ mn ของทุกปมนับป้ายของตัวเองไว้แล้ว ป้ายเลยไม่ต้องถูกดันลงลูกเลย
ถ้าต้องถามค่าของช่วงย่อยถึงจะต้องดัน รายละเอียดอยู่ในบทเซกเมนต์ทรี
ในโจทย์จริง แถว y มีได้ถึงล้าน แต่ค่าในคอลัมน์เปลี่ยนได้แค่ที่จุดเริ่มของช่วงและจุดถัดจากปลายช่วง
โค้ดเก็บใบเฉพาะจุดเหล่านั้น ไม่เกิน 2P + 1 ใบ เรียกว่าการบีบพิกัด (coordinate compression)
ตัวอย่างนี้มีจุดแบบนั้น 3 จุด เท่ากับจำนวนแถวพอดี เลยไม่มีอะไรให้บีบ
ถ้าใบส่วนเกินถูกตั้งเป็นศูนย์แทนอนันต์ รากจะเห็นศูนย์เสมอ ผมพลาดจุดนี้จริง เรื่องเต็มอยู่ในส่วนโค้ดท้ายหน้า
ต่อหนึ่งรอบของไบนารีมี 2P เหตุการณ์ แต่ละครั้งแตะปมราว 4 log₂(2P + 1) ปม
(วางป้ายสองฝั่ง แล้วคำนวณ mn ใหม่ตามทางขึ้นรากอีกสองสาย) ชุดที่ 2 ได้ราว 7.7 × 10⁷ ใช้เวลาไม่เกิน 0.31 วินาที
วิธีนี้ผ่านชุดที่ 1 ได้ด้วย เพราะงบศูนย์ก็เป็นกรณีหนึ่งของ "ค่าต่ำสุดไม่เกินงบ"
เรื่องจริงที่เกิดขึ้น
หน้านี้เวอร์ชันก่อนส่งโค้ดที่ใช้วิธีของตอนที่ 5 และ 6 กับทุกชุด และเขียนไว้ว่าใช้เวลา 3.8 วินาที ผมหาไม่เจอว่าตัวเลขนั้นวัดจากเครื่องไหนหรืออินพุตแบบไหน
ตอนเขียนใหม่ ผมวัดโค้ดเวอร์ชันนั้นซ้ำ 7 ครั้งกับอินพุตสุ่ม P = 400,000 งบศูนย์ ได้ 8.8 ถึง 11.8 วินาที
เกินลิมิต 5 วินาทีทุกครั้ง ชุดงบศูนย์เลยเปลี่ยนไปใช้สองตัวชี้ ซึ่งใช้เวลาไม่เกิน 2.1 วินาทีกับอินพุตแบบเดียวกัน
งานของตอนที่ 6 ยังคูณด้วย 20 รอบของไบนารี และชุดที่ 3 มีก้อนมากกว่าชุดที่ 2 ราว 13 เท่า งานเลยทะลุงบ
วิธีที่ 1 (ตอนที่ 1)
ราว 8 × 10¹⁸ แม้หลังค้นไบนารีแล้ว
ตรงตามนิยามที่สุด ใช้ได้แค่ตารางจิ๋ว
ใช้เป็นตัวตรวจ
วิธีที่ 2 (ตอนที่ 4)
ราว 6.4 × 10¹³
ลองแค่ x ที่ติดขอบขวาของก้อน ใช้ได้เฉพาะงบศูนย์
ผ่านชุดที่ 1
วิธีที่ 3 (ตอนที่ 5 และ 6)
ราว 1.3 × 10⁹ ใช้เวลา 8.8 ถึง 11.8 วินาที
ใช้ได้ทุกงบ แต่ทำซ้ำ 20 รอบ
ผ่านชุดที่ 1 และ 2
วิธีที่ 4 (ตอนนี้)
ราว 6.7 × 10⁷ ใช้เวลาไม่เกิน 2.1 วินาที
ไม่ต้องเดาด้านยาว เดินรอบเดียว ใช้ได้เฉพาะงบศูนย์
ผ่านชุดที่ 1 และ 3 รวมกับวิธีที่ 3 ได้ 100 คะแนน
ตัวนับนี้บอกได้แค่ลำดับขนาด ตามตัวนับ วิธีที่ 3 เกินงบราว 2.6 เท่า ส่วนเวลาที่วัดได้เกินลิมิต 1.8 ถึง 2.4 เท่า
จุดที่ตัวนับพลาดชัดที่สุดคือวิธีที่ 4 มันนับได้ราว 6.7 × 10⁷ น้อยกว่าวิธีที่ 3 ในชุดที่ 2 (7.7 × 10⁷) แต่ใช้เวลานานกว่าราว 7 เท่า
ต้นไม้ของวิธีที่ 4 มีสี่อาร์เรย์รวมราว 64 เมกะไบต์ ส่วนต้นไม้ของชุดที่ 2 ใช้ราว 2 เมกะไบต์
ผมเดาว่าการแตะหน่วยความจำที่ใหญ่เกินแคช (หน่วยความจำเร็วที่อยู่ติดซีพียู) แพงกว่ามาก บวกกับการอ่านอินพุตสี่แสนบรรทัด แต่ยังไม่ได้วัดแยกว่าส่วนไหนกินเวลาเท่าไร
งบเป็นศูนย์อีกครั้ง ทำให้ใช้ท่าที่ไม่ต้องตรึงด้านยาวได้ คือสองตัวชี้ (two pointers)
แทนที่จะเดาด้านแล้วถาม เราให้ช่วงคอลัมน์ l ถึง r ของฐานยืดหดเอง ความกว้างคือ r − l + 1
บทสองตัวชี้กับหน้าต่างเลื่อนอธิบายท่านี้ตั้งแต่ต้น
ตอนนี้กลับมาคิดบนตารางจริง ก้อนที่แตะช่วงคอลัมน์นี้ปิดแถวของมันทั้งหมด
ฐานกว้าง r − l + 1 วางได้เมื่อมีแถวว่างติดกันยาวอย่างน้อยเท่าความกว้าง
วิธีเดิน
r ไปทีละคอลัมน์ ก้อนที่เริ่มที่คอลัมน์ r เข้ามาปิดแถวของมันl ทีละคอลัมน์ ก้อนที่จบที่คอลัมน์ l หลุดออกก่อนขยับ
ถ้าช่วง l ถึง r วางไม่ได้ ช่วง l ถึง r + 1 ก็วางไม่ได้ เพราะกว้างขึ้นแต่แถวว่างมีแต่จะลดลง
l เลยเดินหน้าอย่างเดียว และทั้งสองตัวชี้เดินรวมกันไม่เกิน 2M ก้าว
ต้นไม้ของตอนนี้เป็นอีกต้นหนึ่ง คนละต้นกับตอนที่ 6 มันอยู่บนแถว y จริงทั้ง N แถวโดยไม่บีบพิกัด
และแต่ละปมจำสี่อย่าง
cov: จำนวนก้อนที่คลุมทั้งช่วงของปมpre และ suf: แถวว่างที่ติดขอบล่างและขอบบนของช่วงยาวเท่าไรbst: แถวว่างติดกันยาวสุดในช่วง ค่าที่รากคือตัวเลขที่ข้อ 2 ของวิธีเดินถามต่อค่าสองลูกที่ราก ตอน r = 4
ก้อนที่อยู่ในช่วงตอนนั้นคือ B แถวว่างคือ 3, 4, 5 ลูกซ้ายดูแลแถว 1 ถึง 3 ลูกขวาดูแลแถว 4 ถึง 5
ปมที่ cov มากกว่าศูนย์ไม่มีแถวว่างเลย ปมอื่นต่อค่าจากลูกแบบข้างบน
ก้อนเข้าและออกที่ช่วงแถวเดิมเสมอ cov เลยไม่ต้องดันลงลูก เหมือนป้ายค้างของตอนที่ 6
ลองเดินกับตารางเดิม แต่ตั้งงบเป็น 0 (ห้ามแตะก้อน A ถึง D เลย) คำตอบของการไล่ทุกขนาดคือ 3
| r | ก้อนที่เข้า | ก้อนที่หลุด | l | แถวว่างยาวสุด | กว้าง | ดีที่สุด |
|---|---|---|---|---|---|---|
| 1 | A | 2 | 0 | 0 | 0 | |
| 2 | A | 3 | 5 | 0 | 0 | |
| 3 | 3 | 5 | 1 | 1 | ||
| 4 | B | 3 | 3 | 2 | 2 | |
| 5 | C | B | 5 | 3 | 1 | 2 |
| 6 | 5 | 3 | 2 | 2 | ||
| 7 | 5 | 3 | 3 | 3 | ||
| 8 | D | C | 7 | 2 | 2 | 3 |
| 9 | 8 | 2 | 2 | 3 |
ก้อนเข้าครั้งเดียวและออกครั้งเดียว ครั้งละแวะราว 4 log₂ N ปม รวมการสร้างต้นไม้และการเดิน r ล้านก้าวได้ราว 6.7 × 10⁷
โค้ดเต็มเลยแยกทางตามงบ งบศูนย์ใช้สองตัวชี้ (ชุดที่ 1 และ 3) งบมากกว่าศูนย์ใช้ไบนารีกับการกวาดเส้น (ชุดที่ 2 ซึ่งโจทย์รับประกันว่า P ไม่เกิน 30,000)
| ส่วนในโค้ด | อธิบายไว้ที่ |
|---|---|
build, add | ตอนที่ 6 · ต้นไม้ค่าต่ำสุด ป้ายค้าง และใบส่วนเกินที่ต้องเป็นอนันต์ |
ok ส่วนผู้สมัครแกน y | ตอนที่ 6 · การบีบพิกัด |
ok ส่วนเหตุการณ์แกน x และลูปกวาด | ตอนที่ 3 (สี่เหลี่ยมของมุม) และตอนที่ 5 (เข้า ออก ถามราก) |
zpull, zadd, solveZero | ตอนที่ 7 · สองตัวชี้กับต้นไม้แถวว่าง |
main | ตอนที่ 7 (แยกตามงบ) และตอนที่ 2 (ค้นหาแบบไบนารี) |
ท่าที่ยกไปข้ออื่นได้: เมื่อต้นทุนยังคูณจำนวนรอบของการค้นหาแบบไบนารีอยู่ ให้ดูว่ามีชุดทดสอบไหนที่ถามคำถามง่ายกว่า (ข้อนี้คือ "มีที่ว่างไหม") แล้วตอบมันในรอบเดียว
ในหน้านี้