programming.in.th · ข้อ 2040
แทรกอัศวินที่มาสายลงในแถวตรงไหนก็ได้ ให้เขาชนะมากที่สุด กับดักอยู่ที่คำตอบเป็นตำแหน่งที่แทรก ไม่ใช่จำนวนรอบที่ชนะ กุญแจคือก้อนของตำแหน่งที่ถูกยุบในแต่ละรอบเหมือนกันหมดไม่ว่าจะแทรกตรงไหน คิดครั้งเดียวใช้ได้ทุกตำแหน่ง แล้วมองว่าเขาไม่มีวันตายจนถึงรอบที่แพ้จริง
ทหาร N คนยืนเรียงเป็นเส้นตรง นับตำแหน่งจาก 0 ถึง N-1
ทุกคนมีค่าพลังไม่ซ้ำกัน ตั้งแต่ 0 คืออ่อนที่สุด ถึง N-1 คือแรงที่สุด
เจ้าของงานจะเรียกประลองทั้งหมด C รอบ รอบหนึ่งเขาเอ่ยเลขสองตัวคือ S กับ E
ทุกคนที่ยืนอยู่ในช่วง S ถึง E ของแถวตอนนั้น ต้องสู้กัน
คนที่พลังมากที่สุดชนะและยืนอยู่ที่เดิม ที่เหลือออกจากสนาม แล้วคนที่ยังอยู่ก็เบียดเข้าหากันและนับเลขตำแหน่งกันใหม่
ตอนนี้มีคนยืนอยู่แล้ว N-1 คน ตามลำดับที่โจทย์ให้มา เหลืออัศวินหนึ่งคนที่มาสาย
พลังของเขาคือ R เราเลือกได้ว่าจะให้เขาไปแทรกตรงไหนของแถว
เป้าหมายคือให้เขาชนะให้ได้มากรอบที่สุด รอบที่เขาไม่ได้ถูกเรียกลงสนามไม่นับ
และพอเขาแพ้ครั้งแรกเขาก็ออกจากสนามเหมือนคนอื่น
อินพุต / ขอบเขต / เอาต์พุต
N C R
ต่อด้วย N-1 บรรทัด บรรทัดที่ i คือค่าพลังของคนที่ยืนอยู่ตำแหน่งที่ i ตั้งแต่แรก
แล้วปิดด้วย C บรรทัด บรรทัดละ S กับ E เรียงตามลำดับรอบ
1 < N < 100000, 1 < C < N-1,
เวลา 1 วินาที หน่วยความจำ 64 เมกะไบต์ และในแต่ละรอบ 0 ≤ S < E
โดย E ไม่เกินตำแหน่งสุดท้ายของแถวตอนนั้น
N-1 คนรวมกับ R
เป็นเลข 0 ถึง N-1 ครบพอดีไม่ซ้ำกัน หน้านี้เช็กซ้ำตอนบิลด์ทุกครั้ง
| Input | Output |
|---|---|
| 5 3 3 1 0 2 4 1 3 0 1 0 1 | 1 |
| 10 7 3 6 2 9 8 1 5 4 0 7 4 5 1 2 0 1 4 5 3 5 1 3 0 1 | 1 |
อ่านตัวอย่างนี้ยังไง
ชุดแรกอ่านว่า มีคน 5 คน ประลอง 3 รอบ คนมาสายมีพลัง 3
อีก 4 บรรทัดถัดมาคือแถวเดิม (1, 0, 2, 4) แล้ว 3 บรรทัดสุดท้าย
คือคู่ S E ของแต่ละรอบ (1 3 / 0 1 / 0 1)
เลขคู่นี้เป็นตำแหน่งในแถวขณะนั้น ไม่ใช่ตำแหน่งตั้งต้น พอจบรอบหนึ่งแถวสั้นลงและนับเลขใหม่
รอบต่อไปคำว่า "ตำแหน่ง 0" จึงหมายถึงคนละคนกับรอบที่แล้วได้
ทีนี้ถึงจุดที่พลาดกันมากที่สุด เอาต์พุตมีหน่วยเป็นตำแหน่งที่แทรก ไม่ใช่จำนวนรอบที่ชนะ ถ้าเราลองแทรกครบทุกช่องของชุดแรก จำนวนรอบที่เขาชนะคือ 1, 2, 2, 2, 0 ตามลำดับช่อง 0 ถึง 4 ค่ามากที่สุดคือ 2 แต่คำตอบที่โจทย์เฉลยคือ 1 ใครที่คิดถูกทุกอย่างแล้วพิมพ์ 2 ออกไปจะผิด ทั้งที่คิดไม่ผิดเลย เลข 1 มาจากช่องที่ได้ 2 รอบซึ่งมีอยู่ 3 ช่อง (ช่อง 1, 2, 3) แล้วหยิบช่องที่เลขน้อยที่สุด
กฎ "เท่ากันให้เอาช่องเลขน้อยสุด" ตรงนี้ผมอนุมานจากตัวอย่างสองชุด ไม่ได้อ่านเจอในโจทย์ และชุดที่ชี้ขาดคือชุดแรกชุดเดียว เพราะชุดที่สองจำนวนรอบสูงสุดคือ 1 ซึ่งบังเอิญเท่ากับเลขคำตอบพอดี มันจึงแยกสองความหมายไม่ออก (ชุดที่สองมีช่องที่ดีที่สุดอยู่ 6 ช่อง และช่องเลขน้อยสุดในนั้นคือ 1) ต้นฉบับของโจทย์นี้รับตำแหน่งที่ดีที่สุดช่องไหนก็ได้ ถ้าส่งแล้วโดนตีกลับทั้งที่มั่นใจในอัลกอริทึม ให้สงสัยกติกาการตัดสินเสมอกันก่อนสงสัยอัลกอริทึม
ใบ้
ท่าที่ตรงที่สุดคือลองแทรกให้ครบทุกช่องแล้วจำลองแถวดู แต่มีช่องให้ลอง N ช่อง
แต่ละช่องต้องเดิน C รอบ แค่คูณสองตัวนี้ที่ขอบเขตใหญ่สุดก็เป็น 9,999,600,003 ครั้งแล้ว
ยังไม่นับว่าการเดินหนึ่งรอบต้องกวาดคนในช่วง S ถึง E อีก
ลองกลับไปดูรูปข้างบนอีกที เทียบสองฝั่งดูว่ารอบที่หนึ่งกินคนกลุ่มไหนของแถวตั้งต้น ฝั่งซ้ายมันกินช่อง 1 ถึง 3 ฝั่งขวามันกินช่องอะไร แล้วสองฝั่งนี้เหมือนกันหรือต่างกัน
ชุดอุ่นเครื่อง เดินตามที่อ่านมาข้างบนได้เลย ลองกดช่องท้ายแถวดูด้วยว่าเกิดอะไรขึ้น
กดช่องสีเทาช่องใดช่องหนึ่งเพื่อแทรกอัศวินที่มาสายลงตรงนั้น
ช่องที่ลองแล้วจะติดเลขจำนวนรอบที่ชนะไว้ กดซ้ำได้ไม่จำกัด
แถวสีน้ำเงินคืออัศวินที่ยืนอยู่แล้วพร้อมค่าพลัง ช่องสีเทาคั่นระหว่างพวกเขาคือที่ว่างที่แทรกได้
รายการรอบอยู่เหนือกระดาน ตัวเลข S กับ E นับบนแถว ณ ตอนนั้น
ลองให้ครบทั้งสี่ชุดก่อนนะครับ โดยเฉพาะชุดที่สองกับชุดที่สาม พอเริ่มเดาถูกว่าช่องไหนดีก่อนกด ค่อยไปดูเฉลย
ที่มาของแนวคิดนี้
ผมไม่ได้ลองท่าอื่นแล้วทิ้ง เพราะท่าตรง ๆ ตายตั้งแต่คูณเลข ช่องให้แทรกมี N ช่อง
รอบมี C รอบ และขอบเขตบอกว่า C ไปได้ถึง N-2
แค่ 99,999 คูณ 99,997 ก็ 9,999,600,003 แล้ว ยังไม่นับการกวาดช่วงในแต่ละรอบ
สิ่งที่ผมต้องหาจึงไม่ใช่ท่าที่เร็วขึ้นนิดหน่อย แต่เป็นของบางอย่างที่คิดครั้งเดียวแล้วใช้ได้กับทุกช่องพร้อมกัน
แรงกดดันนั้นพาไปสู่คำถามว่า อะไรในโจทย์ที่ไม่เปลี่ยนเมื่อเราย้ายที่แทรก ลองนั่งเทียบสองฝั่งของรูปข้างบน จะเห็นว่าคนที่รอดต่างกันแน่นอน ฝั่งซ้ายรอบแรกเหลือ 3 ฝั่งขวาเหลือ 4 แต่ขอบของกลุ่มคนที่ถูกยุบรวมกัน อยู่ที่เดิมทั้งสองฝั่ง ตรงนี้เป็นจุดที่คิดกลับด้านกันง่ายมาก เพราะสัญชาตญาณบอกว่าถ้าคนรอดเปลี่ยน ทุกอย่างข้างหลังก็ต้องเปลี่ยนตาม
บทเรียนที่เอาไปใช้ข้ออื่นได้คือ เวลาต้องตอบคำถามเดิมซ้ำ ๆ กับอินพุตที่ต่างกันนิดเดียว ให้ไล่หาว่าส่วนไหนของกระบวนการเป็นของกลาง แล้วคิดส่วนนั้นให้จบก่อนครั้งเดียว
ตกลงกันเรื่องคำก่อน คำว่าก้อนในที่นี้หมายถึงช่วงของตำแหน่งตั้งต้น
คือเลข 0 ถึง N-1 ของแถวก่อนประลองรอบแรก ไม่ใช่เลขตำแหน่งที่ขยับไปมาระหว่างเกม
ทุกคนที่ยังยืนอยู่ในสนามเป็นตัวแทนของก้อนหนึ่งก้อน ตอนเริ่มเกมทุกคนเป็นตัวแทนของก้อนขนาดหนึ่งคือตัวเอง
พอจบรอบที่กิน S ถึง E ก้อนของคนเหล่านั้นก็เชื่อมต่อกันเป็นก้อนเดียว
โดยมีผู้ชนะเป็นตัวแทนคนใหม่
ประเด็นอยู่ตรงนี้ ก้อนใหม่คือ [A, B] เดิมเสมอ ไม่ว่าใครจะเป็นคนชนะ
เพราะการชนะเปลี่ยนแค่ว่าใครถือก้อนนี้ไว้ ไม่ได้เปลี่ยนว่าก้อนกินตำแหน่งตั้งต้นไหนบ้าง
และค่าพลังของใครก็ไม่เกี่ยวกับเรื่องนี้เลย ลำดับของก้อนจึงคิดครั้งเดียวจบ ใช้ได้กับทั้ง N ช่องพร้อมกัน
การหาขอบ [A, B] ของแต่ละรอบก็แค่ถามว่า คนที่ยืนอยู่ตำแหน่งที่ S ของแถวตอนนี้
คือตัวแทนของก้อนที่เริ่มตรงไหน และคนที่ตำแหน่ง E คือก้อนที่จบตรงไหน
ท่ามาตรฐานคือทำเครื่องหมายว่าช่องตั้งต้นไหน "ยังเป็นหัวก้อนอยู่" แล้วถามหาหัวก้อนลำดับที่ S
ซึ่งเป็นคำถามที่ BIT ตอบได้ด้วยการไต่ลงจากบนสุด
ใช้เวลา log N ต่อครั้ง ทั้งหมดจึงเป็น C log N
ปัญหาที่เหลือคือ พอคนมาสายแพ้แล้วเขาจะถูกถอดออกจากแถว แถวหลังจากนั้นก็เลยขึ้นกับว่าเราแทรกเขาไว้ตรงไหน ก้อนที่เราเพิ่งคิดไว้ใช้ได้เพราะมันไม่ขึ้นกับค่าพลัง แต่มันคิดจากแถวที่มีเขาอยู่ตลอดเวลา
ทางออกคือสมมติว่าเขาอมตะ คือปล่อยให้เขาอยู่ในแถวต่อไปแม้แพ้แล้ว แล้วนับเฉพาะช่วงก่อนที่เขาจะแพ้ครั้งแรก เหตุผลคือก่อนถึงรอบที่เขาแพ้ครั้งแรก แถวอมตะกับแถวจริงเหมือนกันทุกประการ เขายังไม่เคยถูกถอด ทุกอย่างจึงเดินเหมือนกันเป๊ะ และรอบที่เขาแพ้ก็ยังเป็นรอบเดียวกัน สิ่งที่ต่างกันเริ่มตั้งแต่หลังรอบนั้น ซึ่งเป็นช่วงที่เราไม่นับคะแนนอยู่แล้ว
พูดอีกแบบ คำตอบของช่อง p คือจำนวนรอบที่เขาชนะในแถวอมตะ นับไปจนถึงรอบแรกที่เขาแพ้แล้วหยุด
พอคิดแบบนี้ได้ ก้อนชุดเดิมก็ใช้ได้ตลอดทั้งเกม เพราะแถวอมตะมีคนเท่าเดิมเสมอไม่ว่าจะแทรกตรงไหน
สมมติเราแทรกเขาไว้ช่อง p เขาก็ยึดช่องตั้งต้นหมายเลข p
เขาจะได้ลงสนามในรอบที่ j ก็ต่อเมื่อ p อยู่ในก้อน [A, B] ของรอบนั้น
และเขาจะชนะรอบนั้นก็ต่อเมื่อในก้อนนั้นไม่มีใครแรงกว่าเขาเลย
คำถามคือ "ในก้อนนั้นมีใครบ้าง" คำตอบสวยกว่าที่คิด ถ้าแทรกเขาที่ช่อง p
ช่องตั้งต้นหมายเลข i จะถูกครองโดย K[i] เมื่อ i มาก่อน p
และถูกครองโดย K[i-1] เมื่อ i มาหลัง p
ดังนั้นคนที่อยู่ในก้อน [A, B] นอกจากตัวเขาเองก็คือ K[A] ถึง K[B-1] พอดี
ไม่ว่า p จะเป็นเท่าไร นี่คือสิ่งที่รูปที่สองพยายามบอกด้วยการวาดกล่องอัศวินคร่อมรอยต่อของช่อง
เงื่อนไขชนะจึงกลายเป็น "ผลรวมของ K[A] ถึง K[B-1] ที่แรงกว่า R เท่ากับศูนย์"
ซึ่งเป็นคำถามผลรวมช่วงบนอาเรย์คงที่ ตอบด้วยผลรวมสะสมได้ใน O(1)
และไม่มี p โผล่มาในเงื่อนไขเลย แต่ละรอบจึงเหลือข้อมูลแค่บิตเดียว
คือรอบนี้เป็นรอบที่ชนะหรือรอบที่แพ้ สำหรับทุกคนที่อยู่ในก้อนพร้อมกัน
ที่เหลือคือกวาดรอบตามลำดับแล้วดูแลช่องทั้ง N ช่องไปพร้อมกัน รอบที่เป็นบิตชนะ
แปลว่าทุกช่องในก้อนที่ยังไม่ตายได้แต้มเพิ่มหนึ่ง ซึ่งคือการบวกทั้งช่วงแล้วถามทีละจุด
ส่วนรอบที่เป็นบิตแพ้ แปลว่าทุกช่องในก้อนที่ยังไม่ตาย ตายพร้อมกันตรงนั้น และคะแนนของมันถูกล็อกไว้
การไล่เก็บเฉพาะช่องที่ยังไม่ตายในช่วงหนึ่ง คือตัวชี้ "ช่องที่ยังไม่ตายถัดไป" แบบ
DSU ซึ่งแต่ละช่องถูกแตะตอนตายครั้งเดียวในชีวิต รวมทั้งหมดจึงเป็น N ครั้ง
| รอบ | S ถึง E (แถวตอนนั้น) | ก้อนของช่องตั้งต้น | คนแรงกว่า R ในก้อน | บิต | ผลต่อทุกช่องที่ยังไม่ตาย |
|---|---|---|---|---|---|
| 1 | 1 ถึง 3 | [1, 3] | 0 | ชนะ | บวก 1 ให้ช่อง 1, 2, 3 ที่ยังไม่ตาย |
| 2 | 0 ถึง 1 | [0, 3] | 0 | ชนะ | บวก 1 ให้ช่อง 0, 1, 2, 3 ที่ยังไม่ตาย |
| 3 | 0 ถึง 1 | [0, 4] | 1 | แพ้ | ช่อง 0, 1, 2, 3, 4 ที่ยังไม่ตาย ตกรอบพร้อมกัน ล็อกคะแนนไว้ตรงนั้น |
| ช่องที่แทรก | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| จำนวนรอบที่ชนะ | 1 | 2 | 2 | 2 | 0 |
รวมค่าใช้จ่ายทั้งหมด หาก้อนด้วย BIT เป็น C log N ตรวจบิตด้วยผลรวมสะสมเป็น C
บวกแต้มทั้งช่วงด้วย BIT อีกตัวเป็น C log N และถามทีละจุดตอนตายอีก N log N
รวมเป็น O((N + C) log N) ซึ่งพอสำหรับขอบเขตนี้แบบเหลือ ๆ
ชุด "มีคนแรงกว่ายืนขวางอยู่" ในเกมเป็นชุดที่ลงโทษท่าไปยืนข้างคนที่อ่อนที่สุดในแถวเพราะคิดว่าจะชนะง่ายที่สุด ตำแหน่งที่สัญชาตญาณนั้นพาไปคือช่อง 3 ซึ่งได้ 0 รอบ ขณะที่ช่อง 4 ได้ 2 รอบ เหตุผลอ่านออกจากก้อนได้ตรง ๆ คือการอยู่ข้างคนอ่อนไม่ได้แปลว่าจะได้สู้กับคนอ่อน เพราะรอบหนึ่งกินทั้งก้อน ไม่ได้กินแค่เพื่อนบ้านสองข้าง สิ่งที่ต้องดูคือ ก้อนที่ครอบช่องนั้นมีคนแรงกว่าเขาอยู่หรือเปล่า
ชุด "คนมาสายแรงที่สุดในสนาม" เป็นอีกมุมหนึ่ง คนมาสายมีพลัง 6 ซึ่งแรงที่สุดในสนาม บิตของทุกรอบจึงเป็นชนะหมด คำถามเลยกลายเป็นว่าช่องไหนอยู่ในก้อนของรอบมากที่สุด ช่อง 4 อยู่ในก้อนแค่ 0 รอบ ส่วนช่อง 2 อยู่ครบ 3 รอบ ชุดนี้แยกสองเรื่องที่มักปนกันออกจากกัน คือ "แพ้หรือไม่แพ้" กับ "ได้ลงสนามหรือไม่ได้ลง"
ส่วนชุด "ดีเท่ากันหลายช่อง" มีช่องที่ดีที่สุดอยู่ 4 ช่อง คือช่อง 3, 4, 5, 6 ทุกช่องได้ 2 รอบเท่ากัน คำตอบที่ต้องพิมพ์คือ 3 ซึ่งเป็นช่องเลขน้อยที่สุดในกลุ่มนั้น ตามกติกาที่ผมอนุมานไว้ตั้งแต่ต้นหน้า
ที่รันจริง: ไล่ครบทุกกรณีของ N ตั้งแต่ 3 ถึง 7 ทุกค่า R
ทุกการสลับของค่าพลังที่เหลือ และทุกลำดับรอบที่ถูกกติกา รวม 25,929,102 เคส
เทียบเวกเตอร์คำตอบทุกช่องกับตัวตรวจ ไม่ต่างกันสักเคส จากนั้นสุ่มเพิ่มอีก 12,144 เคส
ครอบคลุมรูปแบบรอบสี่แบบ โดยบังคับให้ R เป็นคนแรงสุด อ่อนสุด แรงรองสุด และสุ่ม
ก็ไม่ต่างกันอีก รวมทั้งหมด 25,941,246 ครั้งที่เทียบกัน และตัวอย่างทั้งสองชุดในโจทย์ตอบ 1 ถูกต้อง
ส่วนเวลา ที่ N เท่ากับ 100000 และ C เท่ากับ 99998 วัดได้ 0.03 ถึง 0.07 วินาที
จากชุดหนักที่จัดรูปไว้ห้าแบบ เทียบกับลิมิต 1 วินาที
(สังเกตว่า C ไม่เกิน N-2 ชุดที่ N กับ C เท่ากันเป๊ะจึงสร้างไม่ได้)
หน้านี้ยังรันด่านตรวจของตัวเองตอนบิลด์อีก 3,034 เคสสุ่ม เทียบวิธีที่สอนกับการจำลองตรง ๆ
ท่าแรกคือ เวลาต้องตอบคำถามเดิมซ้ำกับอินพุตที่ต่างกันนิดเดียว ให้มองหาโครงสร้างที่เป็นของกลาง ที่นี่คือลำดับของก้อน ซึ่งไม่สนใจทั้งค่าพลังและตำแหน่งที่แทรก พอแยกของกลางออกมาได้ งานที่เหลือต่อหนึ่งช่องก็เล็กลงจนกวาดทีเดียวได้ทั้งหมด
ท่าที่สองคือ การสมมติให้ตัวละครอมตะเพื่อให้กระบวนการเป็นของกลาง ใช้ได้ทุกครั้งที่การตายของตัวละคร เกิดขึ้นแค่ครั้งเดียวและเราสนใจแค่ช่วงก่อนหน้านั้น เงื่อนไขที่ต้องเช็กให้ชัดคือ โลกอมตะกับโลกจริงต้องเหมือนกันจนถึงจุดที่เราหยุดนับ ถ้าเงื่อนไขนี้ไม่จริง ท่านี้ใช้ไม่ได้
ท่าที่สามเป็นเรื่องนิสัยมากกว่าอัลกอริทึม คืออ่านให้ชัดว่าโจทย์ขออะไรเป็นเอาต์พุต ข้อนี้ตัวอย่างชุดที่สองปิดบังความต่างไว้พอดีเพราะเลขบังเอิญเท่ากัน เหลือชุดแรกชุดเดียวที่ชี้ขาด เวลาเจอโจทย์ที่คำตอบเป็น "ตำแหน่งที่ดีที่สุด" ให้เช็กตัวอย่างทุกชุดว่ามันแยก "ค่าที่ดีที่สุด" กับ "ตำแหน่งที่ให้ค่านั้น" ออกจากกันได้จริงไหม
หาลำดับก้อนของตำแหน่งตั้งต้นครั้งเดียวด้วย BIT เปลี่ยนแต่ละรอบให้เหลือบิตเดียวว่าชนะหรือแพ้
ด้วยผลรวมสะสมของคนที่แรงกว่า R แล้วกวาดรอบทีเดียวโดยบวกแต้มทั้งช่วงเวลาชนะ
และฆ่าทั้งช่วงเวลาแพ้ ปิดท้ายด้วยการตอบตำแหน่งที่เลขน้อยที่สุดในบรรดาช่องที่ดีที่สุด
ในหน้านี้