programming.in.th · ข้อ 2025
ปิดอุโมงค์หนึ่งเส้นเปิดใหม่หนึ่งเส้น ให้ห้องที่ไกลกันที่สุดใกล้กันที่สุด บทนี้ตัดมิติของคำถามทิ้งด้วยการเขียนสูตรออกมาก่อน แล้วหาความยาวของสองก้อนให้ครบทุกเส้นด้วยการไล่ค่าขึ้นลงรอบเดียว
ตัวตุ่นขุดห้องไว้ N ห้อง แล้วเชื่อมด้วยอุโมงค์จนเดินจากห้องไหนไปห้องไหนก็ได้ และมีทางเดียวเสมอ
ซึ่งก็คือรูปของต้นไม้ ระยะทางระหว่างสองห้องคือจำนวนอุโมงค์ที่ต้องผ่าน
แขกบางคนบ่นว่าเดินระหว่างบางคู่ห้องไกลเกินไป ตัวตุ่นจึงจะรื้อ ปิดอุโมงค์หนึ่งเส้น แล้วเปิดเส้นใหม่หนึ่งเส้น โดยยังต้องเดินถึงกันได้ทุกห้องเหมือนเดิม เป้าหมายคือทำให้ระยะระหว่างสองห้องที่ไกลกันที่สุดน้อยที่สุด
อินพุต / ขอบเขต / เอาต์พุต
N จากนั้นอีก N ลบหนึ่งบรรทัด
แต่ละบรรทัดคือหมายเลขห้องสองห้องที่อุโมงค์เส้นนั้นเชื่อม
1 ≤ N ≤ 300,000 ห้องหมายเลข 1 ถึง N
เวลา 3 วินาที หน่วยความจำ 128 เมกะไบต์
N น้อยกว่า 30 และ 70% มี N น้อยกว่า 3,000
นอกจากนี้ถ้าตอบบรรทัดแรกถูกบรรทัดเดียว ก็ยังได้ราว 70% ของค่าชุดนั้น
| Input | Output |
|---|---|
| 4 1 2 2 3 3 4 | 2 1 2 1 3 |
| 7 1 3 2 3 2 7 4 3 7 5 3 6 | 3 2 3 7 3 |
ชุดแรกเป็นทางเดินยาวสี่ห้อง ระยะไกลสุดตอนแรกคือ 3 พอรื้อแล้วเหลือ 2 ส่วนชุดที่สองลดจาก 4 เหลือ 3 แผนที่หน้านี้พิมพ์ออกมาไม่เหมือนของโจทย์ แต่ได้ค่าน้อยที่สุดเท่ากัน ซึ่งโจทย์อนุญาต
ใบ้
พอปิดอุโมงค์ไปหนึ่งเส้น ต้นไม้จะแตกเป็นสองก้อนเสมอ และอุโมงค์ใหม่ต้องเชื่อมสองก้อนนั้นเข้าด้วยกัน ไม่งั้นจะมีห้องที่เดินไปไม่ถึง
ถ้าสองก้อนถูกกำหนดมาแล้ว คำถามที่เหลือคือควรต่อที่ห้องไหนของแต่ละก้อน ลองนึกถึงห้องที่ "ไกลจากห้องอื่นในก้อนของตัวเองน้อยที่สุด" ดู
ทางเดินเส้นเดียวคือกรณีที่มองออกง่ายที่สุด ตัดตรงไหนแล้วได้อะไร ลองเล่นให้ครบทุกเส้น
กดที่เส้นเพื่อเลือกอุโมงค์ที่จะปิดก่อน
ยังไม่ได้เลือกอะไร
ปิดหนึ่งเส้นแล้วผังจะแตกเป็นสองก้อน จากนั้นกดหนึ่งห้องในแต่ละก้อนเพื่อเปิดอุโมงค์ใหม่
ลองผังที่สามดูจะเห็นเรื่องที่ขัดความรู้สึกอยู่หน่อย คือบางครั้งเส้นที่ควรปิด ไม่ใช่เส้นที่ดู "อยู่ตรงกลาง" ของรูป แต่เป็นเส้นที่ทำให้สองก้อนที่ได้ยาวพอ ๆ กัน
ที่มาของแนวคิดนี้
ข้อนี้ผมเริ่มจากนับจำนวนทางเลือกก่อน มันมีเส้นให้ปิด N ลบหนึ่งเส้น
และมีคู่ห้องให้ต่อได้ถึง N ยกกำลังสองคู่ ถ้าลองทุกอย่างจริง ๆ
ที่ N เท่ากับสามแสนมันจบไม่ได้แน่ ผมจึงถามว่าอันไหนตัดสินใจแทนได้เลย
โดยไม่ต้องลอง
คำตอบมาจากการเขียนสูตรของระยะไกลสุดหลังต่อ ถ้าต่อห้อง x กับห้อง y
ระยะไกลสุดคือค่ามากสุดของสามอย่าง คือระยะไกลสุดในก้อนแรก ในก้อนที่สอง
และทางที่วิ่งข้ามอุโมงค์ใหม่ซึ่งยาวเท่ากับ ระยะไกลสุดจาก x ในก้อนตัวเอง บวกหนึ่ง บวกระยะไกลสุดจาก y
สองตัวแรกไม่ขึ้นกับ x กับ y เลย เหลือแค่ตัวที่สามที่เราคุมได้
และมันจะน้อยที่สุดเมื่อเลือกห้องที่ไกลจากคนอื่นในก้อนตัวเองน้อยที่สุด ซึ่งก็คือจุดกึ่งกลาง
พอเห็นตรงนี้ ปัญหาก็เหลือมิติเดียว คือเลือกเส้นที่จะปิด เท่านั้น ที่เหลือคำนวณได้ทันที บทเรียนที่ยกไปข้ออื่นได้คือ เวลามีตัวแปรหลายตัว ให้เขียนสูตรของคำตอบออกมาก่อน แล้วดูว่าตัวแปรไหนหายไปจากสูตร หรือมีค่าที่ดีที่สุดตายตัว ตัวนั้นเลิกเป็นตัวเลือกได้ทันที
ปิดเส้นหนึ่งแล้วได้สองก้อน ให้ก้อนแรกมีระยะไกลสุดในตัวเองเท่ากับ d1
และก้อนที่สองเท่ากับ d2 ค่าพวกนี้ไม่เปลี่ยนไม่ว่าจะต่ออุโมงค์ใหม่ตรงไหน
เพราะอุโมงค์ใหม่ไม่ได้ทำให้ทางภายในก้อนเดียวกันสั้นลง
ส่วนทางที่วิ่งข้ามอุโมงค์ใหม่ ยาวเท่ากับระยะไกลสุดจากปลายฝั่งนี้ บวกหนึ่ง บวกระยะไกลสุดจากปลายฝั่งโน้น ค่านี้น้อยที่สุดเมื่อเลือกห้องที่มีระยะไกลสุดน้อยที่สุดในก้อนของมัน ซึ่งเรียกว่าจุดกึ่งกลาง (center) และค่าที่ได้เรียกว่ารัศมี (radius) ซึ่งเท่ากับเส้นผ่านศูนย์กลางหารสองปัดขึ้นเสมอ
// ปิดเส้นหนึ่งแล้วได้สองก้อน ยาว d1 กับ d2
// ต่อที่จุดกึ่งกลางของทั้งสองก้อน ทางที่วิ่งข้ามอุโมงค์ใหม่จึงยาว รัศมี + รัศมี + 1
// คำตอบของเส้นนี้คือตัวที่ใหญ่ที่สุดในสามตัว
int s = max(max(d1, d2), (d1 + 1) / 2 + (d2 + 1) / 2 + 1); ค่าของเส้นที่ปิด คือค่ามากที่สุดในสามตัว ระยะไกลสุดของก้อนซ้าย ของก้อนขวา และรัศมีของสองก้อนบวกหนึ่ง
ลองปิดทีละเส้นบนผัง 9 ห้อง ซึ่งตอนแรกไกลสุด 5
| ปิดอุโมงค์ | ก้อนซ้ายยาว | ก้อนขวายาว | รัศมีรวมบวกหนึ่ง | ได้ค่า |
|---|---|---|---|---|
1 ถึง 2 | 0 | 5 | 4 | 5 |
2 ถึง 3 | 3 | 4 | 5 | 5 |
3 ถึง 4 | 5 | 1 | 5 | 5 |
4 ถึง 5 | 5 | 0 | 4 | 5 |
3 ถึง 6 | 5 | 1 | 5 | 5 |
6 ถึง 7 | 5 | 0 | 4 | 5 |
2 ถึง 8 | 4 | 1 | 4 | 4 |
8 ถึง 9 | 4 | 0 | 3 | 4 |
ผังนี้ตอนแรกไกลสุด 5 ห้อง หลังรื้อเหลือ 4 ห้อง โดยปิดอุโมงค์ 2 ถึง 8 แล้วเปิดเส้นใหม่ระหว่างห้อง 3 กับห้อง 8 ตัวเลขทุกช่องมาจากการเดินจริงบนผังนี้ตอนสร้างหน้า
เหลือปัญหาเดียวคือ ต้องรู้ d1 กับ d2 ของทุกเส้น
ถ้าปิดทีละเส้นแล้วเดินวัดใหม่ จะเป็น N คูณ N ซึ่งที่สามแสนคือเก้าหมื่นล้าน
ท่าที่ใช้คือปักรากแล้วไล่ค่าสองรอบ รอบแรกไล่จากใบขึ้นมาหาราก เก็บสองอย่างต่อปม คือความสูงของกิ่ง และเส้นผ่านศูนย์กลางภายในกิ่งนั้น ค่าพวกนี้ตอบฝั่ง ก้อนที่เป็นกิ่ง ได้ทันที
รอบที่สองไล่จากรากลงไปหาใบ เก็บว่าส่วนที่เหลือทั้งหมด ซึ่งก็คือทุกอย่างนอกกิ่งนี้ มีความสูงเท่าไรและมีเส้นผ่านศูนย์กลางเท่าไร ค่าของปมหนึ่งคำนวณจากพ่อของมัน โดยต้องไม่นับกิ่งของตัวเอง จึงต้องเก็บสามอันดับแรกของความสูงในบรรดาลูก และสองอันดับแรกของเส้นผ่านศูนย์กลาง เพื่อให้ตัดตัวเองออกได้เสมอ
โน้ต · ทำไมต้องสามอันดับ ไม่ใช่สองอันดับ
เวลาคิดค่าของลูกตัวหนึ่ง เราต้องการสองทางที่ยาวที่สุดจากพ่อ โดยไม่นับทางที่ลงมาหาลูกตัวนี้ ถ้าลูกตัวนี้เป็นอันดับหนึ่ง เราจะใช้อันดับสองกับอันดับสาม ถ้ามันเป็นอันดับสอง เราจะใช้อันดับหนึ่งกับอันดับสาม สามอันดับจึงเป็นจำนวนที่พอดี
จุดที่ทำให้โค้ดสั้นลงมาก คือเราไม่ต้องรู้ว่าจุดกึ่งกลางของทุกเส้นอยู่ที่ไหน
เรารู้แค่ตัวเลข d1 กับ d2 ก็เลือกเส้นที่ดีที่สุดได้แล้ว
พอรู้ว่าเส้นไหนชนะ ค่อยเดินกว้างสองรอบต่อข้างครั้งเดียว เพื่อหาห้องที่เป็นจุดกึ่งกลางจริง ๆ
ตัวเลขคำนวณให้ครบทุกเส้น แต่ตำแหน่งจริงคำนวณให้เฉพาะเส้นที่ชนะ
เวลาที่วัดได้บนเครื่องผม ที่ N เท่ากับ 300,000 ทั้งทางเดินยาวสามแสนห้อง
ต้นไม้สุ่ม และดาวดวงเดียว ใช้เวลา 0.19 วินาที จากลิมิต 3 วินาที
ระวัง · ความลึกของต้นไม้
ต้นไม้สามแสนห้องอาจเป็นทางเดินยาวสามแสนห้อง ถ้าเขียนการไล่ค่าด้วยการเรียกซ้ำ สแต็กของระบบจะแตกก่อนจะได้คำตอบ โค้ดนี้จึงเรียงปมด้วยการเดินกว้างเก็บไว้ในอาเรย์ แล้วไล่ย้อนอาเรย์แทนขาลง และไล่ไปข้างหน้าแทนขาขึ้น
เรื่องนี้มีเขียนไว้ละเอียดใน DP บนต้นไม้ ซึ่งเป็นบทปูพื้นของท่านี้
ท่าแรกคือรัศมีเท่ากับเส้นผ่านศูนย์กลางหารสองปัดขึ้นเสมอในต้นไม้ ซึ่งเป็นข้อเท็จจริงที่ใช้ซ้ำได้ทุกครั้งที่ต้องเอาต้นไม้สองต้นมาต่อกัน ท่าที่สองคือคำนวณตัวเลขให้ครบทุกทางเลือก แต่คำนวณของจริงเฉพาะทางเลือกที่ชนะ ซึ่งตัดงานหนักออกไปทั้งก้อนโดยไม่เสียความถูกต้อง
ทบทวนพื้นฐาน · หาเส้นผ่านศูนย์กลางด้วยการเดินกว้างสองรอบ
วิธีที่สั้นที่สุดคือเดินกว้างจากห้องไหนก็ได้ ไปเจอห้องที่ไกลที่สุดเรียกว่า u
แล้วเดินกว้างจาก u อีกรอบ ห้องที่ไกลที่สุดรอบนี้คืออีกปลายของเส้นผ่านศูนย์กลางพอดี
ระหว่างทางเก็บพ่อไว้ด้วย แล้วเดินย้อนกลับมาครึ่งทาง ก็จะได้จุดกึ่งกลาง
ท่านี้ใช้ได้เฉพาะกับต้นไม้ ไม่ใช่กราฟทั่วไป เพราะมันอาศัยข้อเท็จจริงว่า ทางระหว่างสองห้องมีทางเดียว ถ้าโจทย์ไหนมีวงจร ท่านี้จะให้คำตอบผิดเงียบ ๆ
| ทาง | ขอบเขตที่มันไหว | งานคร่าว ๆ |
|---|---|---|
| ปิดทุกเส้น แล้วเดินวัดสองก้อนใหม่ทุกครั้ง | N ไม่เกินราวสามพัน | 300,000 คูณ 300,000 คือเก้าหมื่นล้าน |
| ไล่ค่าขึ้นลงรอบเดียว แล้วเดินกว้างเฉพาะเส้นที่ชนะ | N ถึง 300,000 | ราว 900,000 ครั้ง |
พอเขียนสูตรของระยะไกลสุดหลังต่อออกมา ตำแหน่งที่ควรต่อก็ถูกล็อกเป็นจุดกึ่งกลางทันที เหลือแค่ต้องรู้ความยาวของสองก้อนสำหรับทุกเส้นที่ปิดได้ ซึ่งไล่ค่าขึ้นลงรอบเดียวก็ครบ
ในหน้านี้