programming.in.th · ข้อ 2000

โมบาย: ยุบกิ่งทั้งกิ่งให้เหลือป้ายสามแบบ จนไม่เหลือทางให้ตัดสินใจผิด

DP บนต้นไม้แบบที่ค่าซึ่งส่งขึ้นไปหาพ่อไม่ใช่ตัวเลข แต่เป็นป้ายบอกทรงของกิ่ง ท่าที่ได้กลับไปคือการมองหาสิ่งที่การกระทำในโจทย์เปลี่ยนไม่ได้ ซึ่งตัดหน้าตาที่เป็นไปได้สองยกกำลังแสนแบบทิ้งในประโยคเดียว

★★★☆☆ treedfsgreedy อ่าน 9 นาที 6 กันยายน 2026

โจทย์ · โมบายที่ไอค์ยอมรับได้

คุณต้องซื้อของขวัญให้ลูกของพี่ชายชื่อไอค์ ปัญหาคือไอค์มีรสนิยมเฉพาะตัวมาก เขาชอบเฉพาะของที่เรียงลำดับได้ และของที่คุณไปเจอในร้านคือโมบาย (mobile) งานประดับที่แขวนห้อยลงมาจากเพดานคล้ายปลาตะเพียนแขวนของไทย

โมบายหนึ่งชุดประกอบด้วยแกนแนวนอนหลายอัน ปลายทั้งสองข้างของแต่ละแกนมีเส้นลวดห้อยลงมา ปลายเส้นลวดอาจเป็นแกนอันถัดไป หรือเป็นของเล่นหนึ่งชิ้นก็ได้ พูดในภาษาโครงสร้างข้อมูลคือมันเป็น ต้นไม้ทวิภาค (binary tree) ที่แกนคือโหนดภายใน และของเล่นคือใบ

ไอค์จะพอใจก็ต่อเมื่อโมบายเข้าเงื่อนไขสองข้อพร้อมกัน หนึ่ง ของเล่นทุกชิ้นต้องอยู่ในชั้นที่ต่างกันไม่เกินหนึ่งชั้น (ชั้นของของเล่นคือจำนวนแกนที่ต้องไต่ลงมาจากเพดานกว่าจะถึงมัน) และสอง สำหรับของเล่นที่อยู่ต่างชั้นกัน ชิ้นที่อยู่ชั้นลึกกว่าต้องอยู่ทางซ้ายของชิ้นที่อยู่ชั้นตื้นกว่าเสมอ

สิ่งเดียวที่คุณทำได้คือสลับปลายซ้ายกับปลายขวาของแกนอันใดก็ได้ ปลดของที่ห้อยอยู่ทั้งสองข้างออกแล้วสลับกันติดกลับเข้าไป การสลับหนึ่งครั้งไม่ได้ยุ่งกับลำดับของแกนหรือของเล่นที่อยู่ต่ำลงไปจากจุดนั้น มันแค่พลิกกิ่งทั้งกิ่งไปอยู่อีกฝั่ง คำถามคือต้องสลับอย่างน้อยกี่ครั้งโมบายถึงจะเข้าตาไอค์

4 2 5 6 3 1 2 3 3 3 3 3 3
โมบายที่โจทย์ให้มา ของเล่นเจ็ดชิ้นอยู่ชั้น 2 กับชั้น 3 ซึ่งห่างกันหนึ่งชั้นพอดี แต่ชิ้นที่ชั้น 2 ดันอยู่ซ้ายสุด ผิดเงื่อนไขข้อสอง (วาดประกอบโดยผู้เขียน)

อินพุต / ขอบเขต / เอาต์พุต

EXAMPLE
InputOutput
6
2 3
-1 4
5 6
-1 -1
-1 -1
-1 -1
2

ตรงไหนที่ทำให้ข้อนี้ไม่ง่าย

แกนแต่ละอันสลับหรือไม่สลับก็ได้ โมบายที่มี 6 แกนจึงมีหน้าตาที่เป็นไปได้ 64 แบบ ซึ่งไล่ดูให้ครบได้สบาย แต่พอ n เป็นหนึ่งแสน จำนวนแบบคือสองยกกำลังหนึ่งแสน ไล่ไม่ไหวแม้แต่จะเขียนเลขนั้นลงกระดาษ

จุดที่ปลดล็อกข้อนี้อยู่ในบรรทัดที่อ่านผ่านตาไปง่ายที่สุดของโจทย์ การสลับปลายแกนไม่เปลี่ยนชั้นของของเล่นชิ้นใดเลย เพราะกิ่งทั้งกิ่งย้ายไปอีกฝั่งของแกนเดิม ระยะจากเพดานลงมาถึงของเล่นแต่ละชิ้นจึงเท่าเดิมเป๊ะ สิ่งที่เปลี่ยนได้มีอย่างเดียว คือลำดับซ้ายขวา

ในตัวอย่างของโจทย์ ชั้นของของเล่นทั้ง 7 ชิ้นอ่านจากซ้ายไปขวาคือ 2 3 3 3 3 3 3 ไม่ว่าจะสลับกี่ครั้ง ตัวเลขชุดนี้จะยังเป็นชุดเดิมเสมอ เปลี่ยนได้แค่ลำดับที่มันเรียงกันเท่านั้น เงื่อนไขข้อแรกของไอค์จึงตัดสินได้ทันทีตั้งแต่ยังไม่สลับอะไรเลย ถ้าชั้นลึกสุดกับชั้นตื้นสุดห่างกันเกินหนึ่ง ตอบ -1 ได้เลย

ใบ้

ลองมองกิ่งหนึ่งกิ่ง (แกนหนึ่งอันกับทุกอย่างที่ห้อยอยู่ใต้มัน) แล้วถามว่า ถ้าเราจะตัดสินใจว่าแกนที่อยู่เหนือมัน ต้องสลับหรือเปล่า เราจำเป็นต้องรู้อะไรเกี่ยวกับกิ่งนี้บ้าง รู้ว่ามันมีของเล่นกี่ชิ้นไหม รู้หน้าตาข้างในทั้งหมดไหม หรือจริง ๆ แล้วมีข้อมูลแค่ไม่กี่แบบที่เพียงพอ แล้วถ้ามีแค่ไม่กี่แบบ มันมีกี่แบบกันแน่?

ลองเอง · สลับแกนให้ไอค์ยอมรับ

สามแกน พอให้เห็นว่าการสลับทำอะไรกับลำดับ

ชั้นของของเล่น อ่านจากซ้ายไปขวา

กดสลับแกนเพื่อพลิกกิ่งซ้ายขวา เป้าหมายคือชั้นของของเล่นต้องลึกก่อนตื้น

สลับไปแล้ว 0 ครั้ง

การสลับไม่เปลี่ยนชั้นของของเล่นชิ้นไหนเลย มันเปลี่ยนแค่ลำดับซ้ายขวา

ถ้าลองแล้วรู้สึกว่าบางชุดยังไงก็ไม่ผ่าน ให้ดูที่ชุดของชั้นก่อน มันบอกได้ตั้งแต่ยังไม่สลับเลยว่ามีทางหรือไม่มี

เฉลย ตอนที่ 1 · กิ่งหนึ่งกิ่งมีหน้าตาได้แค่สามแบบ

ที่มาของแนวคิดนี้

ตัวเลขที่บีบข้อนี้คือสองยกกำลังหนึ่งแสน ซึ่งไม่ใช่จำนวนที่ "เยอะไปหน่อย" แต่เป็นจำนวนที่เขียนลงกระดาษยังไม่ได้ เวลาเจอเลขระดับนั้น ผมไม่มองหาวิธีไล่ให้เร็วขึ้น เพราะไม่มีตัวคูณไหนช่วยได้ สิ่งที่ต้องหาคือเหตุผลว่าทำไมทางเลือกส่วนใหญ่ถึงไม่ใช่ทางเลือกจริง

เหตุผลนั้นอยู่ในบรรทัดที่อ่านผ่านตาง่ายที่สุด คือการสลับปลายแกนไม่เปลี่ยนชั้นของของเล่นเลยสักชิ้น พอชั้นเปลี่ยนไม่ได้ ชุดของชั้นก็ถูกล็อกตั้งแต่ก่อนเราตัดสินใจอะไร เหลือให้เลือกแค่ลำดับซ้ายขวา และเมื่อของเล่นทุกชิ้นต้องอยู่แค่สองชั้น กิ่งหนึ่งกิ่งจึงมีได้แค่สามหน้าตา

ตรงนี้ผมไม่กล้าเชื่อจากการนั่งไล่กรณีในหัว เพราะข้ออ้างที่ว่า "ไม่มีแบบที่สี่" เป็นข้ออ้างที่พังง่ายถ้ามองไม่ครบ ผมจึงเขียนตัวไล่ทุกอย่างจริง ๆ คือสุ่มโมบายเล็ก ๆ แล้วลองสลับทุกแกนทุกวิธีที่เป็นไปได้ เก็บเฉพาะรูปที่ผ่านเงื่อนไขของไอค์ ได้มา 616 รูป แล้วไล่ติดป้ายให้ทุกกิ่งในทุกรูป ผลคือไม่มีกิ่งไหนเลยที่หลุดจากสามแบบนี้ ตัวไล่นั้นไม่รู้จักคำว่าป้ายด้วยซ้ำ มันแค่กางทุกความเป็นไปได้ออกมา

บทเรียนที่ยกไปข้ออื่นได้คือ เมื่อจำนวนทางเลือกใหญ่จนไล่ไม่ได้ ให้ไปหาสิ่งที่การตัดสินใจเปลี่ยนมันไม่ได้ ของสิ่งนั้นคือส่วนที่ถูกล็อกไว้แล้ว และสิ่งที่เหลือหลังหักมันออก มักเล็กกว่าที่กลัวไว้มาก

ตั้งชื่อชั้นลึกที่สุดของของเล่นทั้งโมบายว่า D จากตอนที่แล้วเรารู้แล้วว่าถ้าจะมีคำตอบ ของเล่นทุกชิ้นต้องอยู่ที่ชั้น D หรือชั้น D - 1 เท่านั้น ทีนี้กิ่งที่ผ่านเงื่อนไขของไอค์แล้ว มีหน้าตาได้แค่สามแบบ

  • เต็มลึก ของเล่นในกิ่งนี้อยู่ชั้น D ทุกชิ้น มันเป็นกิ่งที่แน่นเต็มถึงชั้นล่างสุด
  • เต็มตื้น ของเล่นในกิ่งนี้อยู่ชั้น D - 1 ทุกชิ้น มันเป็นกิ่งที่จบก่อนหนึ่งชั้น
  • ครึ่ง ในกิ่งนี้มีทั้งของชั้น D และชั้น D - 1 ปนกัน โดยของลึกอยู่ซ้ายของของตื้นเรียบร้อยแล้ว

ที่มันมีแค่สามแบบเพราะมีแค่สองชั้นให้เลือก กิ่งหนึ่งจะมีของชั้นลึกอย่างเดียว ชั้นตื้นอย่างเดียว หรือมีทั้งสอง ไม่มีทางเลือกที่สี่ และของเล่นหนึ่งชิ้นก็นับเป็นกิ่งเล็กที่สุด ถ้ามันอยู่ชั้น D มันคือเต็มลึก ถ้าอยู่ชั้น D - 1 มันคือเต็มตื้น

ประเด็นสำคัญคือเรารู้แค่ป้ายสามแบบนี้ก็พอ ไม่ต้องรู้เลยว่าข้างในกิ่งมีแกนกี่อันหรือหน้าตายังไง เพราะสิ่งเดียวที่แกนด้านบนต้องตัดสินใจคือ "เอากิ่งไหนไว้ซ้าย" ซึ่งขึ้นกับป้ายเท่านั้น

แล้วเงื่อนไข "ของลึกอยู่ทางซ้าย" ก็แปลเป็นลำดับของป้ายได้ตรง ๆ เต็มลึกต้องอยู่ซ้ายสุด เต็มตื้นต้องอยู่ขวาสุด ส่วนครึ่งอยู่ตรงกลาง เขียนเป็นลำดับได้ว่า เต็มลึก ก่อน ครึ่ง ก่อน เต็มตื้น

เฉลย ตอนที่ 2 · เก้าคู่ที่เป็นไปได้ ไม่มีอะไรให้เลือกเลย

แกนหนึ่งอันเห็นป้ายของกิ่งซ้ายกับกิ่งขวา รวมเป็นเก้าคู่ ไล่ให้ครบแล้วจะเห็นว่าแทบทุกคู่บังคับคำตอบมาให้แล้ว ไม่ใช่ปัญหาที่ต้องเลือก

แกนหนึ่งอัน · ป้ายของกิ่งซ้ายกับกิ่งขวา
กิ่งซ้ายกิ่งขวาต้องสลับป้ายของแกนนี้
เต็มลึก เต็มลึก ไม่ต้อง เต็มลึก
เต็มลึก ครึ่ง ไม่ต้อง ครึ่ง
เต็มลึก เต็มตื้น ไม่ต้อง ครึ่ง
ครึ่ง เต็มลึก สลับ 1 ครั้ง ครึ่ง
ครึ่ง ครึ่ง ทำไม่ได้ ตอบ -1
ครึ่ง เต็มตื้น ไม่ต้อง ครึ่ง
เต็มตื้น เต็มลึก สลับ 1 ครั้ง ครึ่ง
เต็มตื้น ครึ่ง สลับ 1 ครั้ง ครึ่ง
เต็มตื้น เต็มตื้น ไม่ต้อง เต็มตื้น
3 คู่ที่ต้องสลับคือคู่ที่ป้ายเรียงผิดลำดับ สลับแล้วมันเรียงถูกทันที ส่วน 1 คู่ที่ทำไม่ได้คือครึ่งประกบครึ่ง เพราะไม่ว่าจะสลับหรือไม่ ก็จะมีของชั้นตื้นของกิ่งซ้ายไปยืนอยู่ซ้ายของของชั้นลึกของกิ่งขวาเสมอ

อ่านตารางนี้ทีเดียวได้กฎสามข้อ ข้อแรก ถ้าทั้งสองข้างเป็นครึ่ง จบเลย ตอบ -1 ข้อสอง ถ้าป้ายเรียงผิดลำดับ ก็สลับหนึ่งครั้ง ไม่มีทางเลือกอื่น ข้อสาม ป้ายของแกนนี้คือเต็มลึกเมื่อลูกเป็นเต็มลึกทั้งคู่ เต็มตื้นเมื่อลูกเป็นเต็มตื้นทั้งคู่ นอกนั้นเป็นครึ่ง

เพราะทุกช่องในตารางบังคับมาให้หมด จึงไม่มีการเลือกที่ผิดพลาดได้เลย จำนวนครั้งที่สลับจึงไม่ใช่ค่าที่ต้องไปหา ค่าที่น้อยที่สุด มันเป็นค่าเดียวที่เป็นไปได้ นับไปตามทางแล้วได้เท่าไรก็คือคำตอบ

สังเกตรูปของวิธีนี้ให้ดี เพราะมันมีสองท่าที่จะเจอซ้ำไปทั้งคลัง ท่าแรกคือเราตัดสินใจที่แกนหนึ่งอัน โดยดูแค่ป้ายของลูกสองกิ่ง ไม่มองไปข้างหน้าและไม่ย้อนกลับมาแก้ การตัดสินใจแบบเห็นอะไรดีตรงหน้าก็เอาเลย เรียกว่าท่าโลภ (greedy) ท่าที่สองคือลำดับที่เราเดิน คือต้องรู้ป้ายของลูกให้ครบก่อนจึงตัดสินใจที่พ่อได้ การไล่ลงไปให้สุดก่อนแล้วค่อยเก็บผลย้อนขึ้นมาแบบนี้เรียกว่าการไล่ลึก (DFS) โครงสร้างที่เราไล่อยู่ก็คือต้นไม้ ที่มีแกนบนสุดเป็นราก

ทบทวนพื้นฐาน · ท่าโลภ กับ การไล่ลึก

ท่าโลภ (greedy อ่านว่า "กรีดี" แปลว่าตะกละ) คือการตัดสินใจแต่ละก้าวด้วยสิ่งที่ดีที่สุด เฉพาะหน้า แล้วไม่ย้อนกลับมาแก้ ข้อดีคือเร็วและโค้ดสั้น ข้อเสียคือมันมักจะผิด เพราะการเลือกที่ดีตรงหน้าอาจปิดทางที่ดีกว่าในอนาคต ปกติท่าโลภจึงต้องมาพร้อมเหตุผลว่าทำไมมันถูก ไม่ใช่แค่ลองแล้วตัวอย่างผ่าน

ข้อนี้พิเศษตรงที่เหตุผลนั้นได้มาฟรี ตารางเก้าคู่ข้างบนแสดงว่าทุกสถานการณ์มีทางเดินได้ทางเดียว เมื่อไม่มีทางเลือก ก็ไม่มีทางเลือกผิด ท่าโลภที่ต้องพิสูจน์กันจริงจังหน้าตาไม่เหมือนกันเลย อย่างในโจทย์ใบเรือ ที่ต้องอธิบายว่าทำไมการหย่อนลงกองที่ว่างที่สุดจึงดีที่สุด

การไล่ลึก (depth-first search ย่อว่า DFS) คือการเดินกราฟหรือต้นไม้แบบลงให้สุดทางหนึ่งก่อน แล้วค่อยถอยมาแยกทางถัดไป สิ่งที่ทำให้มันคู่กับต้นไม้ได้ดีคือลูกเสร็จก่อนพ่อเสมอ ค่าที่พ่อต้องใช้จึงพร้อมอยู่แล้วตอนที่ถึงคิวพ่อ ถ้าอยากเห็นท่านี้แบบเต็ม ๆ มีบทปูพื้นเรื่อง DP บนต้นไม้อยู่ในคลังนี้ ซึ่งใช้ลำดับเดียวกันนี้ทั้งบท

เดินตารางนี้จากล่างขึ้นบนกับตัวอย่างในโจทย์ ได้ผลตามนี้ (ชั้นลึกที่สุดของตัวอย่างคือ D = 3)

ตัวอย่างในโจทย์ · เดินจากแกนล่างสุดขึ้นมา
แกนกิ่งซ้ายกิ่งขวาทำอะไรป้ายที่ได้
4 เต็มลึก เต็มลึก ปล่อยไว้ เต็มลึก
2 เต็มตื้น เต็มลึก สลับ ครึ่ง
5 เต็มลึก เต็มลึก ปล่อยไว้ เต็มลึก
6 เต็มลึก เต็มลึก ปล่อยไว้ เต็มลึก
3 เต็มลึก เต็มลึก ปล่อยไว้ เต็มลึก
1 ครึ่ง เต็มลึก สลับ ครึ่ง
สลับสองครั้งคือที่แกน 1 และแกน 2 ตรงกับคำตอบ 2 ของโจทย์
5 6 3 4 2 1 3 3 3 3 3 3 2
โมบายเดิมหลังสลับ 2 ครั้ง ชั้นของของเล่นอ่านจากซ้ายไปขวาเป็น 3 3 3 3 3 3 2 คือลึกก่อนตื้น ตรงตามที่ไอค์ต้องการ (วาดประกอบโดยผู้เขียน)

โมบายที่ชั้นถูกหมด แต่ยังตอบ -1

เงื่อนไข "ครึ่งประกบครึ่ง" ในตารางไม่ใช่ของประดับ มันเกิดขึ้นจริงได้ และเกิดกับโมบายที่ผ่านการตรวจชั้นตั้งแต่ต้นมาแล้วด้วย ตัวอย่างข้างล่างมีของเล่น 6 ชิ้น ชั้นของมันคือ 2 3 3 2 3 3 ซึ่งอยู่แค่สองชั้นติดกัน ผ่านเงื่อนไขข้อแรกสบาย ๆ

4 2 5 3 1 2 3 3 2 3 3
ของชั้นลึกถูกแบ่งไปอยู่คนละข้างของแกนบนสุด แกนบนสุดจึงเห็นครึ่งประกบครึ่ง และไม่มีการสลับชุดไหนแก้ได้ (วาดประกอบโดยผู้เขียน)

กิ่งซ้ายของแกน 1 เป็นครึ่ง กิ่งขวาก็เป็นครึ่ง สลับยังไงก็ยังมีของชั้นตื้นของกิ่งหนึ่งไปยืนคั่นหน้าของชั้นลึกของอีกกิ่งอยู่ดี โปรแกรมจึงตอบ -1 ทั้งที่ตรวจชั้นตอนต้นผ่านฉลุย นี่คือเหตุผลที่การเช็กชั้น อย่างเดียวไม่พอ ต้องเดินตารางจนถึงแกนบนสุดจริง ๆ

โค้ด C++

ทั้งข้อคือสามบรรทัดนี้ วนบนทุกแกนจากล่างขึ้นบน อ่านป้ายของลูกสองข้าง แล้วทำตามตาราง

หัวใจของทั้งข้อ
if (a == 1 && b == 1) { bad = true; break; }   // ครึ่งสองอันประกบกันไม่ได้
if (a > b) { cost++; swap(a, b); }             // ลำดับที่ถูกคือ เต็มลึก ครึ่ง เต็มตื้น
shape[v] = (a == 0 && b == 0) ? 0 : (a == 2 && b == 2) ? 2 : 1;

กับดักที่ทำให้โค้ดถูกแต่ตกรอบคือความลึกของการเรียกซ้ำ โจทย์ไม่ได้รับประกันว่าโมบายจะสมดุล อินพุตที่แกนแต่ละอันมีแกนลูกข้างเดียวยาวติดกันหนึ่งแสนอันเป็นอินพุตที่ถูกกติกาทุกประการ ถ้าเขียนเป็นฟังก์ชันเรียกตัวเอง สแต็กจะล้นก่อนได้คำตอบ ที่นี่จึงเดินด้วยสแต็กที่ประกาศเอง แล้วเก็บลำดับที่พ่ออยู่ก่อนลูกไว้ใน order พออ่านย้อนกลับก็ได้ลูกก่อนพ่อพอดี

อีกจุดที่ต้องอ่านให้ครบคือ -1 มีสองที่มา ที่แรกคือชั้นของของเล่นห่างกันเกินหนึ่งชั้น ซึ่งตรวจได้ก่อนเริ่มเลย ที่สองคือครึ่งประกบครึ่งระหว่างทาง ซึ่งรู้ตอนเดินไปแล้วเท่านั้น โค้ดที่ตรวจแค่ที่แรกจะผ่านตัวอย่างในโจทย์ แล้วไปตกกับเทสที่ซ่อนไว้

ดูโค้ดเต็ม
mobiles.cpp
#include <bits/stdc++.h>
using namespace std;

int n;
int L[100005], R[100005], dep[100005];
int shape[100005];      // 0 = เต็มลึก, 1 = ครึ่ง, 2 = เต็มตื้น

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d %d", &L[i], &R[i]);

    // เดินหาความลึกด้วยสแต็กของตัวเอง ไม่ใช้การเรียกซ้ำ เพราะโมบายอาจลึกถึงแสนชั้น
    vector<int> order;
    order.reserve(n);
    {
        vector<int> st{1};
        dep[1] = 0;
        while (!st.empty()) {
            int v = st.back(); st.pop_back();
            order.push_back(v);
            if (L[v] != -1) { dep[L[v]] = dep[v] + 1; st.push_back(L[v]); }
            if (R[v] != -1) { dep[R[v]] = dep[v] + 1; st.push_back(R[v]); }
        }
    }

    // ชั้นของของเล่นเปลี่ยนไม่ได้ ถ้ามันห่างกันเกินหนึ่งชั้นก็จบตั้งแต่ยังไม่เริ่ม
    int deep = 0, shallow = INT_MAX;
    for (int i = 1; i <= n; i++)
        if (L[i] == -1 || R[i] == -1) {
            deep = max(deep, dep[i] + 1);
            shallow = min(shallow, dep[i] + 1);
        }
    if (deep - shallow > 1) { puts("-1"); return 0; }

    // order เป็นลำดับที่พ่ออยู่ก่อนลูกเสมอ อ่านย้อนกลับจึงได้ลูกก่อนพ่อ
    long long cost = 0;
    bool bad = false;
    for (int idx = (int)order.size() - 1; idx >= 0 && !bad; idx--) {
        int v = order[idx];
        int a = (L[v] == -1) ? (dep[v] + 1 == deep ? 0 : 2) : shape[L[v]];
        int b = (R[v] == -1) ? (dep[v] + 1 == deep ? 0 : 2) : shape[R[v]];

        if (a == 1 && b == 1) { bad = true; break; }   // ครึ่งสองอันประกบกันไม่ได้
        if (a > b) { cost++; swap(a, b); }             // ลำดับที่ถูกคือ เต็มลึก ครึ่ง เต็มตื้น
        shape[v] = (a == 0 && b == 0) ? 0 : (a == 2 && b == 2) ? 2 : 1;
    }

    if (bad) puts("-1");
    else printf("%lld\n", cost);
    return 0;
}
ของแถม: ตัวตรวจสอบที่ผมใช้ก่อนส่ง

ก่อนเชื่อโค้ดข้างบน ผมสุ่มโมบายที่มีแกนไม่เกิน 9 อัน สี่ร้อยชุด แล้วเทียบกับตัวลองสลับทุกแบบข้างล่างนี้ ซึ่งเช็กตรงตามนิยามในโจทย์เป๊ะ ๆ คือเก็บชั้นของของเล่นเรียงจากซ้ายไปขวา แล้วดูว่ามันไม่เพิ่มขึ้นและอยู่ในสองชั้นติดกัน ตรงกันทั้งสี่ร้อยชุด

brute.py
# ตัวตรวจสอบแบบซื่อ ๆ ลองสลับทุกแบบด้วยบิตมาสก์ ใช้ได้แค่ n ราว ๆ 10
# เอาไว้สุ่มเทียบกับโค้ดจริงก่อนส่ง ไม่ใช่โค้ดที่ส่งเข้าระบบตัดสิน
import sys
sys.setrecursionlimit(10000)

d = sys.stdin.read().split()
n = int(d[0])
L = [0] * (n + 1)
R = [0] * (n + 1)
for i in range(1, n + 1):
    L[i] = int(d[2 * i - 1])
    R[i] = int(d[2 * i])

best = None
for mask in range(1 << n):
    l, r = L[:], R[:]
    for i in range(1, n + 1):
        if mask >> (i - 1) & 1:
            l[i], r[i] = r[i], l[i]

    seq = []
    def go(v, dp):
        for c in (l[v], r[v]):
            if c == -1: seq.append(dp + 1)
            else: go(c, dp + 1)
    go(1, 0)

    D = max(seq)
    if min(seq) < D - 1: continue                                  # ชั้นห่างกันเกินหนึ่ง
    if any(seq[i] < seq[i + 1] for i in range(len(seq) - 1)): continue  # ของลึกต้องอยู่ซ้ายเสมอ
    c = bin(mask).count('1')
    if best is None or c < best: best = c

print(-1 if best is None else best)

งานทั้งหมดคือเดินต้นไม้สองรอบ รอบแรกหาความลึก รอบที่สองไล่ป้าย จึงเป็น O(n) เวลาที่วัดได้บนเครื่องผมกับโมบายหนึ่งแสนแกนคือ 60 มิลลิวินาที จากลิมิตหนึ่งวินาที โดยกรณีที่ผมกลัวที่สุด (โซ่ยาวหนึ่งแสนแกน) จบเร็วที่สุดเพราะตรวจชั้นแล้วตอบ -1 ตั้งแต่ต้น

ท่าที่ติดมือกลับไปใช้ได้ทุกครั้งที่โจทย์ให้ทำอะไรบางอย่างกี่ครั้งก็ได้ ก่อนจะเริ่มไล่ว่าจะทำตรงไหนบ้าง ให้ถามก่อนว่าการกระทำนั้นเปลี่ยนอะไรไม่ได้เลย ของที่มันเปลี่ยนไม่ได้ถูกตัดสินไปแล้ว ตั้งแต่ก่อนเราลงมือ จึงตรวจได้ทันทีและมักตัดคำตอบทิ้งได้ก้อนใหญ่ ส่วนของที่มันเปลี่ยนได้คือทั้งหมด ที่เหลือให้ค้นจริง ๆ แล้วถ้าของก้อนนั้นยุบลงเหลือป้ายไม่กี่แบบได้อีก ตารางของคู่ป้ายก็จะเล็กพอจะกางดู ด้วยมือทั้งตาราง ซึ่งเป็นวิธีที่ถูกที่สุดในการรู้ว่ายังเหลือจุดให้ตัดสินใจผิดอยู่ไหม เกณฑ์ตัดสินว่ากิ่งหนึ่งกิ่ง ควรส่งค่าขึ้นไปให้พ่อกี่ค่าอยู่ในบทปูพื้นฐาน DP บนต้นไม้

สรุปบรรทัดเดียว

การสลับปลายแกนเปลี่ยนลำดับได้แต่เปลี่ยนชั้นไม่ได้ กิ่งหนึ่งกิ่งจึงยุบเหลือป้ายสามแบบคือเต็มลึก ครึ่ง เต็มตื้น แล้วทุกแกนแค่จัดป้ายของลูกสองข้างให้เรียงตามลำดับนั้น ซึ่งเป็นการตัดสินใจที่ไม่มีทางเลือกให้พลาดเลยสักแกนเดียว

แหล่งที่มา

  1. โจทย์ Mobiles บน programming.in.th ข้อ 2000 programming.in.th/tasks/2000 (สืบค้น 6 กันยายน 2026)
  2. ต้นทางของโจทย์คือ Asia-Pacific Informatics Olympiad 2007