programming.in.th · ข้อ 2038
ทุกสัปดาห์วัวเจอทางใหม่หนึ่งเส้น แล้วต้องตอบว่าดูแลทางสั้นที่สุดเท่าไรให้ทุกจุดถึงกัน รัน Kruskal ใหม่หมดทุกสัปดาห์เฉียดเวลา บทนี้แสดงว่าทางที่ปิดวงไม่เคยต้องเรียกกลับ จึงถือแค่ต้นไม้ N - 1 เส้นข้ามสัปดาห์
ฟาร์มของชาวนาจอห์นมีจุดสำคัญ N จุด แต่ละจุดถูกป่ากั้นไว้ วัวอยากเลือกดูแลรักษาทางเดินบางเส้น
ให้เดินจากจุดไหนไปจุดไหนก็ได้ ทางเดินสองทิศทาง และแม้ทางจะตัดกันได้ วัวก็ไม่เปลี่ยนทางกลางทาง
จุดที่เปลี่ยนทางได้จึงมีแค่ที่ N จุดนั้น
ทุกสัปดาห์วัวค้นพบทางใหม่หนึ่งเส้น แล้วต้องเลือกว่าสัปดาห์นี้จะดูแลทางไหนบ้าง จากทุกทางที่เคยรู้จักมา เลือกใหม่ได้ทุกสัปดาห์ ทางที่ไม่ได้ดูแลเมื่อสัปดาห์ก่อนก็หยิบกลับมาได้ วัวอยากให้ความยาวรวมของทางที่ดูแลสั้นที่สุด ทางใหม่อาจเชื่อมจุดคู่เดิมที่เคยมีทางแล้ว และสั้นกว่าเดิมก็ได้ เพราะทางในป่าไม่ได้เป็นเส้นตรง
ชื่อโจทย์ maintain อ่านว่า "เมนเทน" แปลว่าดูแลรักษาให้คงสภาพ มาจากภาษาละตินที่แปลตรง ๆ ว่า "ถือไว้ในมือ" (manu tenere) ซึ่งเข้ากับข้อนี้พอดี เพราะทั้งข้อคือคำถามว่าต้องถืออะไรไว้ในมือข้ามสัปดาห์บ้าง
อินพุต / ขอบเขต / เอาต์พุต
N กับ W (จำนวนจุด และจำนวนสัปดาห์)
จากนั้นอีก W บรรทัด บรรทัดละสามจำนวน คือสองจุดที่ทางเชื่อม และความยาว Z ของทางนั้น
N ไม่เกิน 200, W ไม่เกิน 6,000,
Z ไม่เกิน 10,000 เวลา 1 วินาที หน่วยความจำ 64 เมกะไบต์
-1 | Input | Output |
|---|---|
| 4 6 1 2 10 1 3 8 3 2 3 1 4 3 1 3 6 2 1 2 | -1 -1 -1 14 12 8 |
อ่านตัวอย่างนี้ยังไง
สี่จุด หกสัปดาห์ บรรทัดที่ k ของอินพุตคือทางที่เจอในสัปดาห์ที่ k
และบรรทัดที่ k ของเอาต์พุตคือคำตอบของสัปดาห์นั้น เอาต์พุตจึงมีหกบรรทัดเท่าจำนวนสัปดาห์
หน่วยของคำตอบคือความยาวรวม ไม่ใช่จำนวนทาง
3 สัปดาห์แรกตอบ -1 เพราะยังไม่มีทางไหนแตะจุด 4 เลย ทางแรกที่แตะจุด 4 มาในสัปดาห์ที่ 4
สัปดาห์นั้นมีทางให้เลือก 4 เส้น ชุดที่สั้นที่สุดคือ 1-4 (3), 3-2 (3), 1-3 (8)
ยาวรวม 14 อีกสองสัปดาห์ที่เหลือลองคิดเองก่อนนะครับ เกมข้างล่างใช้ชุดนี้เป็นด่านแรก
ใบ้
คำตอบของแต่ละสัปดาห์คือต้นไม้แผ่ทั่วที่เบาที่สุดของทุกทางที่รู้จักตอนนั้น ถ้ายังไม่เคยเจอวิธีหามัน บทปูพื้นฐาน DSU ฝึกข้อ 3 เล่าไว้แล้ว
ส่วนที่ข้อนี้ถามจริง ๆ คือเรื่องเวลา ทำใหม่หมดทุกสัปดาห์ต้องแตะทางกี่เส้น และทางที่สัปดาห์นี้ไม่ได้ใช้ มีโอกาสกลับมาถูกใช้ในสัปดาห์หน้าไหม
สี่จุด หกสัปดาห์ ชุดเดียวกับตัวอย่างข้างบน ลองเลือกทางทีละสัปดาห์แล้วดูว่าคำตอบของคุณตรงกับเอาต์พุตไหม
กดที่ทางเพื่อเลือกดูแลหรือเลิกดูแล ทางสีทองคือทางที่เพิ่งเจอในสัปดาห์นี้
พอไปสัปดาห์ถัดไป ทางที่เลือกไว้ยังค้างอยู่ ลองสังเกตว่าแต่ละสัปดาห์คุณต้องแก้ชุดเดิมมากแค่ไหน
ลองให้ครบทั้งสามชุดก่อนนะครับ โดยเฉพาะสัปดาห์สุดท้ายของชุดสุดท้าย ที่ชุดทางเมื่อสัปดาห์ก่อนดูเหมือนไม่มีอะไรต้องแก้ พอมีคำตอบในใจแล้ว ค่อยเปิดเฉลย
ที่มาของแนวคิดนี้
ข้อนี้ผมจำได้ตั้งแต่อ่านจบว่าเป็นโจทย์ IOI 2003 แต่ก่อนเขียนก็ยังตีราคาท่าตรง ๆ ก่อน เพราะถ้าท่าตรง ๆ ผ่านเวลา ก็ไม่มีเหตุผลต้องคิดอะไรเพิ่ม ท่าตรง ๆ คือทุกสัปดาห์เอาทุกทางที่เคยเจอมาเรียงใหม่ แล้วรัน Kruskal ใหม่หมด
สัปดาห์ที่ k มีทาง k เส้น รวมทั้งหกพันสัปดาห์คือ 18,003,000 เส้น
แล้วยังต้องเรียงทุกสัปดาห์อีก ตัวเลขนี้อยู่ในโซนที่คูณในหัวแล้วตัดสินไม่ได้ ผมจึงเขียนมันจริงแล้ววัด
อินพุตสุ่มเต็มขอบเขตใช้ 0.75 วินาที ส่วนอินพุตที่ความยาวซ้ำกันเยอะ (มีแค่ 1 ถึง 3) ใช้ 1.10 วินาที
เกินลิมิต 1 วินาทีไปแล้ว บนเครื่องตรวจที่ช้ากว่าเครื่องผมก็ยิ่งไม่รอด
บทเรียนที่ยกไปข้ออื่นได้คือ ตัวเลขที่อยู่ระหว่างสิบล้านถึงร้อยล้าน อย่าเดาว่าผ่านหรือไม่ผ่าน ให้วัด
ท่านี้ถูกเสมอ เพราะมันตอบตามนิยามของสัปดาห์นั้นตรง ๆ ผมเก็บมันไว้เป็นตัวเทียบในส่วนโค้ด สิ่งที่ต้องหาต่อจึงเหลือคำถามเดียว คืองานของสัปดาห์ที่แล้ว มีส่วนไหนเอามาใช้ต่อได้บ้าง
ที่มาของแนวคิดนี้
แรงกดดันมาจากตัวเลขในตอนที่ 1 งานเกือบทั้งหมดหมดไปกับทางที่ Kruskal เดินผ่านแล้วข้าม
เพราะต้นไม้มีแค่ N - 1 เส้น ที่เหลือหลายพันเส้นถูกหยิบมาดูทุกสัปดาห์เพื่อจะถูกข้ามซ้ำ
ผมจึงถามว่าทางที่ถูกข้ามในสัปดาห์นี้ มีโอกาสกลับมาถูกใช้ไหม
จุดที่ทำให้ตอบได้คือโจทย์มีแต่เพิ่มทาง ไม่เคยลบทาง ทางที่ถูกข้ามแปลว่าสองปลายของมันถึงกันแล้ว ด้วยทางที่สั้นกว่า และทางพวกนั้นไม่มีวันหายไปจากชุดที่รู้จัก ข้อสรุปคือของที่ต้องถือข้ามสัปดาห์มีแค่ต้นไม้ล่าสุด บทเรียนคือ พอข้อมูลมีแต่เพิ่ม ให้ถามว่าอะไรที่ถูกตัดสินว่าไร้ประโยชน์ไปแล้ว จะถูกตัดสินแบบเดิมตลอดไปหรือเปล่า
ลองดูสัปดาห์สุดท้ายของตัวอย่าง ก่อนสัปดาห์นี้ต้นไม้คือ 1-4 (3), 3-2 (3), 1-3 (6) ทางใหม่ 2-1 (2) เชื่อมสองจุดที่ในต้นไม้ถึงกันอยู่แล้ว มันจึงสร้างวงขึ้นมาหนึ่งวงพอดี คือ 3-2 (3), 1-3 (6), 2-1 (2) วงนี้มีทางเกินมาหนึ่งเส้น ต้องทิ้งหนึ่งเส้นเพื่อกลับเป็นต้นไม้ และเส้นที่ควรทิ้งคือเส้นที่ยาวที่สุดในวง คือ 1-3 (6) คำตอบจึงลดจาก 12 เหลือ 8
กฎข้อนี้มีชื่อ เรียกว่าสมบัติของวง (cycle property) ในวงไหนก็ตาม ทางที่ยาวที่สุดในวงไม่จำเป็นต่อต้นไม้ที่เบาที่สุด เพราะตัดมันออกแล้ว สองปลายของมันยังถึงกันได้ ด้วยทางที่เหลือในวงซึ่งสั้นกว่าทั้งหมด
แกะคำศัพท์ · cycle
cycle อ่านว่า "ไซเคิล" แปลว่าวง คือเดินตามทางแล้ววนกลับมาจุดเดิมได้โดยไม่ใช้ทางซ้ำ รากเดียวกับ bicycle ที่แปลว่าสองวงล้อ มาจากคำกรีกที่แปลว่าวงกลม
ที่ทำให้ข้อนี้ง่ายลงมาก คือทางที่ถูกทิ้งเพราะกฎนี้ ถูกทิ้งได้ถาวร
วงที่ทำให้มันถูกทิ้งประกอบด้วยทางที่สั้นกว่ามัน และในอนาคตมีแต่ทางเพิ่ม ทางที่สั้นกว่าเหล่านั้นอาจโดนทิ้งตามไปบ้าง
แต่ทุกเส้นที่โดนทิ้งก็มีทางที่สั้นกว่ามาต่อแทนเสมอ สองปลายของทางที่ถูกทิ้งไปจึงถึงกันด้วยทางที่สั้นกว่ามันไปตลอด
ของที่ต้องจำข้ามสัปดาห์จึงเหลือแค่ต้นไม้ล่าสุด ไม่เกิน N - 1 เส้น
// ของที่ต้องจำข้ามสัปดาห์มีแค่ต้นไม้ล่าสุด ไม่เกิน N - 1 เส้น
keep.insert(upper_bound(keep.begin(), keep.end(), e), e); // + ทางใหม่ 1 เส้น
// Kruskal บนรายการนี้ ทางที่ปิดวงถูกทิ้ง และไม่ต้องเก็บไว้อีกเลย
งานต่อสัปดาห์เหลือแค่แทรกทางใหม่ลงรายการที่เรียงอยู่แล้ว (เลื่อนของไม่เกิน N ช่อง)
แล้วเดิน Kruskal บนรายการที่ยาวไม่เกิน N เส้น ทางที่ปิดวงในรอบนี้ก็ทิ้งไปเลย
ไม่ต้องหาว่าวงอยู่ตรงไหน เพราะ Kruskal ที่เดินจากสั้นไปยาวจะข้ามทางที่ยาวที่สุดของวงให้เอง
ข้อมูลมีแต่เพิ่ม ทางที่ถูกตัดสินว่ายาวเกินไปแล้วจึงยาวเกินไปตลอดกาล ของที่ต้องถือไว้ในมือมีแค่ต้นไม้ 3 เส้นของตัวอย่าง หรือ N - 1 เส้นของข้อจริง
เดินตัวอย่างในโจทย์ ถือแค่ต้นไม้ล่าสุดข้ามสัปดาห์
| สัปดาห์ | ทางใหม่ | ต้นไม้ที่ถือไว้ | ทิ้ง | คำตอบ |
|---|---|---|---|---|
| 1 | 1-2 (10) | 1-2 (10) | · | -1 |
| 2 | 1-3 (8) | 1-3 (8), 1-2 (10) | · | -1 |
| 3 | 3-2 (3) | 3-2 (3), 1-3 (8) | 1-2 (10) | -1 |
| 4 | 1-4 (3) | 1-4 (3), 3-2 (3), 1-3 (8) | · | 14 |
| 5 | 1-3 (6) | 1-4 (3), 3-2 (3), 1-3 (6) | 1-3 (8) | 12 |
| 6 | 2-1 (2) | 2-1 (2), 1-4 (3), 3-2 (3) | 1-3 (6) | 8 |
คอลัมน์ต้นไม้ไม่เคยยาวเกิน 3 เส้น ส่วนคอลัมน์ทิ้งคือทุกอย่างที่ท่าตรง ๆ ต้องหยิบมาดูซ้ำทุกสัปดาห์ คำตอบทั้งหกบรรทัดคือ -1, -1, -1, 14, 12, 8 ตรงกับเอาต์พุตของโจทย์
มีอีกท่าที่เร็วพอกัน คือถือต้นไม้ไว้เป็นกราฟ พอทางใหม่มาก็เดินหาเส้นทางในต้นไม้ระหว่างสองปลายของมัน แล้วเทียบทางที่ยาวที่สุดบนเส้นทางนั้นกับทางใหม่ ตรงกับภาพข้างบนทุกประการ แต่เขียนยาวกว่าและผมไม่ได้เขียนมันเป็นโค้ดทดสอบ จึงไม่ได้ใส่ไว้ในหน้านี้
เวลาที่วัดได้บนเครื่องผม อินพุต 200 จุด 5,999 สัปดาห์ ท่าเต็มใช้ราว 0.02 วินาที ทั้งแบบความยาวสุ่ม แบบความยาวซ้ำกันเยอะ และแบบที่ทางใหม่สั้นลงทุกสัปดาห์จนต้นไม้ถูกเปลี่ยนตลอด ท่าทำใหม่หมดใช้ 0.75 วินาทีกับชุดแรก และ 1.10 วินาทีกับชุดที่สอง (ชุดที่สามเร็วกว่ามาก เพราะรายการที่ต้องเรียงแทบจะเรียงอยู่แล้ว) ทั้งสองท่าตอบตรงกันทุกบรรทัดในทั้งสามชุด
โจทย์ที่ข้อมูลมีแต่เพิ่มและต้องตอบทุกครั้งที่เพิ่ม ให้ถามก่อนว่าอะไรที่เคยถูกตัดสินว่าไม่ใช้ จะถูกตัดสินแบบเดิมตลอดไปหรือเปล่า ถ้าใช่ ของที่ต้องถือข้ามรอบจะเล็กลงมาก ที่นี่คือจากทุกทางที่เคยเจอ เหลือแค่ต้นไม้ล่าสุด ส่วนของที่เหลือทำแบบเดิมได้ทั้งหมด
| ทาง | ทางที่แตะรวมทุกสัปดาห์ | เวลาที่วัดจริง |
|---|---|---|
| Kruskal ใหม่บนทุกทางที่เคยเจอ | 18,003,000 เส้น (ยังไม่นับการเรียงทุกสัปดาห์) | 0.75 ถึง 1.10 วินาที |
| เก็บแค่ทางของต้นไม้ แทรกทางใหม่แล้ว Kruskal | ไม่เกิน 1,200,000 เส้น | 0.02 วินาที |
ทางที่ปิดวงแล้วยาวที่สุดในวงนั้นไม่มีวันถูกใช้อีก จึงถือแค่ต้นไม้ล่าสุดไว้ แทรกทางใหม่เข้าไป แล้วรัน Kruskal บนไม่เกิน N เส้นทุกสัปดาห์
ในหน้านี้