programming.in.th · ข้อ 2000
DP บนต้นไม้แบบที่ค่าซึ่งส่งขึ้นไปหาพ่อไม่ใช่ตัวเลข แต่เป็นป้ายบอกทรงของกิ่ง ท่าที่ได้กลับไปคือการมองหาสิ่งที่การกระทำในโจทย์เปลี่ยนไม่ได้ ซึ่งตัดหน้าตาที่เป็นไปได้สองยกกำลังแสนแบบทิ้งในประโยคเดียว
คุณต้องซื้อของขวัญให้ลูกของพี่ชายชื่อไอค์ ปัญหาคือไอค์มีรสนิยมเฉพาะตัวมาก เขาชอบเฉพาะของที่เรียงลำดับได้ และของที่คุณไปเจอในร้านคือโมบาย (mobile) งานประดับที่แขวนห้อยลงมาจากเพดานคล้ายปลาตะเพียนแขวนของไทย
โมบายหนึ่งชุดประกอบด้วยแกนแนวนอนหลายอัน ปลายทั้งสองข้างของแต่ละแกนมีเส้นลวดห้อยลงมา ปลายเส้นลวดอาจเป็นแกนอันถัดไป หรือเป็นของเล่นหนึ่งชิ้นก็ได้ พูดในภาษาโครงสร้างข้อมูลคือมันเป็น ต้นไม้ทวิภาค (binary tree) ที่แกนคือโหนดภายใน และของเล่นคือใบ
ไอค์จะพอใจก็ต่อเมื่อโมบายเข้าเงื่อนไขสองข้อพร้อมกัน หนึ่ง ของเล่นทุกชิ้นต้องอยู่ในชั้นที่ต่างกันไม่เกินหนึ่งชั้น (ชั้นของของเล่นคือจำนวนแกนที่ต้องไต่ลงมาจากเพดานกว่าจะถึงมัน) และสอง สำหรับของเล่นที่อยู่ต่างชั้นกัน ชิ้นที่อยู่ชั้นลึกกว่าต้องอยู่ทางซ้ายของชิ้นที่อยู่ชั้นตื้นกว่าเสมอ
สิ่งเดียวที่คุณทำได้คือสลับปลายซ้ายกับปลายขวาของแกนอันใดก็ได้ ปลดของที่ห้อยอยู่ทั้งสองข้างออกแล้วสลับกันติดกลับเข้าไป การสลับหนึ่งครั้งไม่ได้ยุ่งกับลำดับของแกนหรือของเล่นที่อยู่ต่ำลงไปจากจุดนั้น มันแค่พลิกกิ่งทั้งกิ่งไปอยู่อีกฝั่ง คำถามคือต้องสลับอย่างน้อยกี่ครั้งโมบายถึงจะเข้าตาไอค์
อินพุต / ขอบเขต / เอาต์พุต
n จำนวนแกน จากนั้นอีก n บรรทัด บรรทัดที่ i
มีเลขสองตัวคือ l และ r บอกว่าปลายซ้ายและปลายขวาของแกน i ห้อยอะไรอยู่
ค่า -1 แปลว่าเป็นของเล่น ค่าอื่นคือหมายเลขแกน
1 ≤ n ≤ 100 000 แกนที่อยู่ใต้แกน i มีหมายเลขมากกว่า i เสมอ
และแกน 1 คือแกนบนสุด เวลา 1 วินาที หน่วยความจำ 32 เมกะไบต์
-1 | Input | Output |
|---|---|
| 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 ครั้ง
การสลับไม่เปลี่ยนชั้นของของเล่นชิ้นไหนเลย มันเปลี่ยนแค่ลำดับซ้ายขวา
ถ้าลองแล้วรู้สึกว่าบางชุดยังไงก็ไม่ผ่าน ให้ดูที่ชุดของชั้นก่อน มันบอกได้ตั้งแต่ยังไม่สลับเลยว่ามีทางหรือไม่มี
ที่มาของแนวคิดนี้
ตัวเลขที่บีบข้อนี้คือสองยกกำลังหนึ่งแสน ซึ่งไม่ใช่จำนวนที่ "เยอะไปหน่อย" แต่เป็นจำนวนที่เขียนลงกระดาษยังไม่ได้ เวลาเจอเลขระดับนั้น ผมไม่มองหาวิธีไล่ให้เร็วขึ้น เพราะไม่มีตัวคูณไหนช่วยได้ สิ่งที่ต้องหาคือเหตุผลว่าทำไมทางเลือกส่วนใหญ่ถึงไม่ใช่ทางเลือกจริง
เหตุผลนั้นอยู่ในบรรทัดที่อ่านผ่านตาง่ายที่สุด คือการสลับปลายแกนไม่เปลี่ยนชั้นของของเล่นเลยสักชิ้น พอชั้นเปลี่ยนไม่ได้ ชุดของชั้นก็ถูกล็อกตั้งแต่ก่อนเราตัดสินใจอะไร เหลือให้เลือกแค่ลำดับซ้ายขวา และเมื่อของเล่นทุกชิ้นต้องอยู่แค่สองชั้น กิ่งหนึ่งกิ่งจึงมีได้แค่สามหน้าตา
ตรงนี้ผมไม่กล้าเชื่อจากการนั่งไล่กรณีในหัว เพราะข้ออ้างที่ว่า "ไม่มีแบบที่สี่" เป็นข้ออ้างที่พังง่ายถ้ามองไม่ครบ ผมจึงเขียนตัวไล่ทุกอย่างจริง ๆ คือสุ่มโมบายเล็ก ๆ แล้วลองสลับทุกแกนทุกวิธีที่เป็นไปได้ เก็บเฉพาะรูปที่ผ่านเงื่อนไขของไอค์ ได้มา 616 รูป แล้วไล่ติดป้ายให้ทุกกิ่งในทุกรูป ผลคือไม่มีกิ่งไหนเลยที่หลุดจากสามแบบนี้ ตัวไล่นั้นไม่รู้จักคำว่าป้ายด้วยซ้ำ มันแค่กางทุกความเป็นไปได้ออกมา
บทเรียนที่ยกไปข้ออื่นได้คือ เมื่อจำนวนทางเลือกใหญ่จนไล่ไม่ได้ ให้ไปหาสิ่งที่การตัดสินใจเปลี่ยนมันไม่ได้ ของสิ่งนั้นคือส่วนที่ถูกล็อกไว้แล้ว และสิ่งที่เหลือหลังหักมันออก มักเล็กกว่าที่กลัวไว้มาก
ตั้งชื่อชั้นลึกที่สุดของของเล่นทั้งโมบายว่า D จากตอนที่แล้วเรารู้แล้วว่าถ้าจะมีคำตอบ ของเล่นทุกชิ้นต้องอยู่ที่ชั้น
D หรือชั้น D - 1 เท่านั้น ทีนี้กิ่งที่ผ่านเงื่อนไขของไอค์แล้ว มีหน้าตาได้แค่สามแบบ
D ทุกชิ้น มันเป็นกิ่งที่แน่นเต็มถึงชั้นล่างสุดD - 1 ทุกชิ้น มันเป็นกิ่งที่จบก่อนหนึ่งชั้นD และชั้น D - 1 ปนกัน โดยของลึกอยู่ซ้ายของของตื้นเรียบร้อยแล้ว
ที่มันมีแค่สามแบบเพราะมีแค่สองชั้นให้เลือก กิ่งหนึ่งจะมีของชั้นลึกอย่างเดียว ชั้นตื้นอย่างเดียว หรือมีทั้งสอง
ไม่มีทางเลือกที่สี่ และของเล่นหนึ่งชิ้นก็นับเป็นกิ่งเล็กที่สุด ถ้ามันอยู่ชั้น D มันคือเต็มลึก
ถ้าอยู่ชั้น D - 1 มันคือเต็มตื้น
ประเด็นสำคัญคือเรารู้แค่ป้ายสามแบบนี้ก็พอ ไม่ต้องรู้เลยว่าข้างในกิ่งมีแกนกี่อันหรือหน้าตายังไง เพราะสิ่งเดียวที่แกนด้านบนต้องตัดสินใจคือ "เอากิ่งไหนไว้ซ้าย" ซึ่งขึ้นกับป้ายเท่านั้น
แล้วเงื่อนไข "ของลึกอยู่ทางซ้าย" ก็แปลเป็นลำดับของป้ายได้ตรง ๆ เต็มลึกต้องอยู่ซ้ายสุด เต็มตื้นต้องอยู่ขวาสุด ส่วนครึ่งอยู่ตรงกลาง เขียนเป็นลำดับได้ว่า เต็มลึก ก่อน ครึ่ง ก่อน เต็มตื้น
แกนหนึ่งอันเห็นป้ายของกิ่งซ้ายกับกิ่งขวา รวมเป็นเก้าคู่ ไล่ให้ครบแล้วจะเห็นว่าแทบทุกคู่บังคับคำตอบมาให้แล้ว ไม่ใช่ปัญหาที่ต้องเลือก
| กิ่งซ้าย | กิ่งขวา | ต้องสลับ | ป้ายของแกนนี้ |
|---|---|---|---|
| เต็มลึก | เต็มลึก | ไม่ต้อง | เต็มลึก |
| เต็มลึก | ครึ่ง | ไม่ต้อง | ครึ่ง |
| เต็มลึก | เต็มตื้น | ไม่ต้อง | ครึ่ง |
| ครึ่ง | เต็มลึก | สลับ 1 ครั้ง | ครึ่ง |
| ครึ่ง | ครึ่ง | ทำไม่ได้ | ตอบ -1 |
| ครึ่ง | เต็มตื้น | ไม่ต้อง | ครึ่ง |
| เต็มตื้น | เต็มลึก | สลับ 1 ครั้ง | ครึ่ง |
| เต็มตื้น | ครึ่ง | สลับ 1 ครั้ง | ครึ่ง |
| เต็มตื้น | เต็มตื้น | ไม่ต้อง | เต็มตื้น |
อ่านตารางนี้ทีเดียวได้กฎสามข้อ ข้อแรก ถ้าทั้งสองข้างเป็นครึ่ง จบเลย ตอบ -1 ข้อสอง ถ้าป้ายเรียงผิดลำดับ
ก็สลับหนึ่งครั้ง ไม่มีทางเลือกอื่น ข้อสาม ป้ายของแกนนี้คือเต็มลึกเมื่อลูกเป็นเต็มลึกทั้งคู่ เต็มตื้นเมื่อลูกเป็นเต็มตื้นทั้งคู่
นอกนั้นเป็นครึ่ง
เพราะทุกช่องในตารางบังคับมาให้หมด จึงไม่มีการเลือกที่ผิดพลาดได้เลย จำนวนครั้งที่สลับจึงไม่ใช่ค่าที่ต้องไปหา ค่าที่น้อยที่สุด มันเป็นค่าเดียวที่เป็นไปได้ นับไปตามทางแล้วได้เท่าไรก็คือคำตอบ
สังเกตรูปของวิธีนี้ให้ดี เพราะมันมีสองท่าที่จะเจอซ้ำไปทั้งคลัง ท่าแรกคือเราตัดสินใจที่แกนหนึ่งอัน โดยดูแค่ป้ายของลูกสองกิ่ง ไม่มองไปข้างหน้าและไม่ย้อนกลับมาแก้ การตัดสินใจแบบเห็นอะไรดีตรงหน้าก็เอาเลย เรียกว่าท่าโลภ (greedy) ท่าที่สองคือลำดับที่เราเดิน คือต้องรู้ป้ายของลูกให้ครบก่อนจึงตัดสินใจที่พ่อได้ การไล่ลงไปให้สุดก่อนแล้วค่อยเก็บผลย้อนขึ้นมาแบบนี้เรียกว่าการไล่ลึก (DFS) โครงสร้างที่เราไล่อยู่ก็คือต้นไม้ ที่มีแกนบนสุดเป็นราก
ทบทวนพื้นฐาน · ท่าโลภ กับ การไล่ลึก
ท่าโลภ (greedy อ่านว่า "กรีดี" แปลว่าตะกละ) คือการตัดสินใจแต่ละก้าวด้วยสิ่งที่ดีที่สุด เฉพาะหน้า แล้วไม่ย้อนกลับมาแก้ ข้อดีคือเร็วและโค้ดสั้น ข้อเสียคือมันมักจะผิด เพราะการเลือกที่ดีตรงหน้าอาจปิดทางที่ดีกว่าในอนาคต ปกติท่าโลภจึงต้องมาพร้อมเหตุผลว่าทำไมมันถูก ไม่ใช่แค่ลองแล้วตัวอย่างผ่าน
ข้อนี้พิเศษตรงที่เหตุผลนั้นได้มาฟรี ตารางเก้าคู่ข้างบนแสดงว่าทุกสถานการณ์มีทางเดินได้ทางเดียว เมื่อไม่มีทางเลือก ก็ไม่มีทางเลือกผิด ท่าโลภที่ต้องพิสูจน์กันจริงจังหน้าตาไม่เหมือนกันเลย อย่างในโจทย์ใบเรือ ที่ต้องอธิบายว่าทำไมการหย่อนลงกองที่ว่างที่สุดจึงดีที่สุด
การไล่ลึก (depth-first search ย่อว่า DFS) คือการเดินกราฟหรือต้นไม้แบบลงให้สุดทางหนึ่งก่อน แล้วค่อยถอยมาแยกทางถัดไป สิ่งที่ทำให้มันคู่กับต้นไม้ได้ดีคือลูกเสร็จก่อนพ่อเสมอ ค่าที่พ่อต้องใช้จึงพร้อมอยู่แล้วตอนที่ถึงคิวพ่อ ถ้าอยากเห็นท่านี้แบบเต็ม ๆ มีบทปูพื้นเรื่อง DP บนต้นไม้อยู่ในคลังนี้ ซึ่งใช้ลำดับเดียวกันนี้ทั้งบท
เดินตารางนี้จากล่างขึ้นบนกับตัวอย่างในโจทย์ ได้ผลตามนี้ (ชั้นลึกที่สุดของตัวอย่างคือ D = 3)
| แกน | กิ่งซ้าย | กิ่งขวา | ทำอะไร | ป้ายที่ได้ |
|---|---|---|---|---|
4 | เต็มลึก | เต็มลึก | ปล่อยไว้ | เต็มลึก |
2 | เต็มตื้น | เต็มลึก | สลับ | ครึ่ง |
5 | เต็มลึก | เต็มลึก | ปล่อยไว้ | เต็มลึก |
6 | เต็มลึก | เต็มลึก | ปล่อยไว้ | เต็มลึก |
3 | เต็มลึก | เต็มลึก | ปล่อยไว้ | เต็มลึก |
1 | ครึ่ง | เต็มลึก | สลับ | ครึ่ง |
เงื่อนไข "ครึ่งประกบครึ่ง" ในตารางไม่ใช่ของประดับ มันเกิดขึ้นจริงได้ และเกิดกับโมบายที่ผ่านการตรวจชั้นตั้งแต่ต้นมาแล้วด้วย
ตัวอย่างข้างล่างมีของเล่น 6 ชิ้น ชั้นของมันคือ 2 3 3 2 3 3 ซึ่งอยู่แค่สองชั้นติดกัน
ผ่านเงื่อนไขข้อแรกสบาย ๆ
กิ่งซ้ายของแกน 1 เป็นครึ่ง กิ่งขวาก็เป็นครึ่ง สลับยังไงก็ยังมีของชั้นตื้นของกิ่งหนึ่งไปยืนคั่นหน้าของชั้นลึกของอีกกิ่งอยู่ดี
โปรแกรมจึงตอบ -1 ทั้งที่ตรวจชั้นตอนต้นผ่านฉลุย นี่คือเหตุผลที่การเช็กชั้น
อย่างเดียวไม่พอ ต้องเดินตารางจนถึงแกนบนสุดจริง ๆ
ทั้งข้อคือสามบรรทัดนี้ วนบนทุกแกนจากล่างขึ้นบน อ่านป้ายของลูกสองข้าง แล้วทำตามตาราง
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 มีสองที่มา ที่แรกคือชั้นของของเล่นห่างกันเกินหนึ่งชั้น ซึ่งตรวจได้ก่อนเริ่มเลย
ที่สองคือครึ่งประกบครึ่งระหว่างทาง ซึ่งรู้ตอนเดินไปแล้วเท่านั้น โค้ดที่ตรวจแค่ที่แรกจะผ่านตัวอย่างในโจทย์
แล้วไปตกกับเทสที่ซ่อนไว้
งานทั้งหมดคือเดินต้นไม้สองรอบ รอบแรกหาความลึก รอบที่สองไล่ป้าย จึงเป็น O(n)
เวลาที่วัดได้บนเครื่องผมกับโมบายหนึ่งแสนแกนคือ 60 มิลลิวินาที จากลิมิตหนึ่งวินาที
โดยกรณีที่ผมกลัวที่สุด (โซ่ยาวหนึ่งแสนแกน) จบเร็วที่สุดเพราะตรวจชั้นแล้วตอบ -1 ตั้งแต่ต้น
ท่าที่ติดมือกลับไปใช้ได้ทุกครั้งที่โจทย์ให้ทำอะไรบางอย่างกี่ครั้งก็ได้ ก่อนจะเริ่มไล่ว่าจะทำตรงไหนบ้าง ให้ถามก่อนว่าการกระทำนั้นเปลี่ยนอะไรไม่ได้เลย ของที่มันเปลี่ยนไม่ได้ถูกตัดสินไปแล้ว ตั้งแต่ก่อนเราลงมือ จึงตรวจได้ทันทีและมักตัดคำตอบทิ้งได้ก้อนใหญ่ ส่วนของที่มันเปลี่ยนได้คือทั้งหมด ที่เหลือให้ค้นจริง ๆ แล้วถ้าของก้อนนั้นยุบลงเหลือป้ายไม่กี่แบบได้อีก ตารางของคู่ป้ายก็จะเล็กพอจะกางดู ด้วยมือทั้งตาราง ซึ่งเป็นวิธีที่ถูกที่สุดในการรู้ว่ายังเหลือจุดให้ตัดสินใจผิดอยู่ไหม เกณฑ์ตัดสินว่ากิ่งหนึ่งกิ่ง ควรส่งค่าขึ้นไปให้พ่อกี่ค่าอยู่ในบทปูพื้นฐาน DP บนต้นไม้
การสลับปลายแกนเปลี่ยนลำดับได้แต่เปลี่ยนชั้นไม่ได้ กิ่งหนึ่งกิ่งจึงยุบเหลือป้ายสามแบบคือเต็มลึก ครึ่ง เต็มตื้น แล้วทุกแกนแค่จัดป้ายของลูกสองข้างให้เรียงตามลำดับนั้น ซึ่งเป็นการตัดสินใจที่ไม่มีทางเลือกให้พลาดเลยสักแกนเดียว
ในหน้านี้