programming.in.th · ข้อ 2040

Tournament: แทรกอัศวินมาสายตรงไหนดี

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

★★★★☆ BITdsuoffline อ่าน 13 นาที เผยแพร่ 11 กันยายน 2026 อัปเดต 20 ตุลาคม 2026

โจทย์ · อัศวินมาสาย

ทหาร N คนยืนเรียงเป็นเส้นตรง นับตำแหน่งจาก 0 ถึง N-1 ทุกคนมีค่าพลังไม่ซ้ำกัน ตั้งแต่ 0 (อ่อนสุด) ถึง N-1 (แรงสุด) เจ้าของงานเรียกประลองทั้งหมด C รอบ แต่ละรอบเป็นแบบนี้

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

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

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

EXAMPLE
InputOutput
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

อ่านตัวอย่างนี้ยังไง

ชุดแรกอ่านทีละบรรทัดได้แบบนี้

ถ้าแทรกคนมาสายไว้ช่อง p แถวตอนเริ่มยาว N คน คือ K[0] ถึง K[p-1] ตามด้วยเขา แล้ว K[p] ถึง K[N-2] หน้านี้เรียกตำแหน่ง 0 ถึง N-1 ของแถวตอนเริ่มว่าช่องตั้งต้น

เลขช่องตั้งต้นไม่เปลี่ยนตลอดเกม ส่วนเลขคู่ S E นับบนแถวขณะนั้น พอจบรอบหนึ่งแถวสั้นลงและนับเลขใหม่ รอบต่อไป "ตำแหน่ง 0" อาจเป็นคนละคนกับรอบที่แล้ว

จุดที่พลาดกันมากที่สุดคือหน่วยของเอาต์พุต มันคือตำแหน่งที่แทรก ไม่ใช่จำนวนรอบที่ชนะ ลองแทรกครบทุกช่อง 0 ถึง 4 ของชุดแรก เขาชนะ 1, 2, 2, 2, 0 รอบตามลำดับ

ค่ามากสุดคือ 2 ได้จาก 3 ช่อง (ช่อง 1, 2, 3) คำตอบ 1 คือช่องเลขน้อยสุดในนั้น ใครพิมพ์ 2 ออกไปจะผิดทั้งที่คิดถูกหมด

ชุดแรกบอกสองเรื่อง: เอาต์พุตเป็นเลขช่อง และเมื่อเสมอกันให้ตอบช่องเลขน้อยสุด

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

กฎเสมอกันนี้ผมอนุมานจากสองชุดนั้น โจทย์ไม่ได้เขียนไว้ และต้นฉบับ IOI รับช่องที่ดีที่สุดช่องไหนก็ได้

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

ใบ้

วิธีตรง ๆ คือลองแทรกครบทุกช่องแล้วจำลองแถว แต่ช่องมี N ช่อง ช่องละ C รอบ ที่ขอบเขตใหญ่สุดก็ 9,999,600,003 คู่ (ช่อง, รอบ) แล้ว ยังไม่นับการกวาดช่วงในแต่ละรอบ

กลับไปดูรูปข้างบน รอบที่หนึ่งกินคนกลุ่มไหนของแถวตั้งต้น (เลขบนสุด) ฝั่งซ้ายกินช่อง 1 ถึง 3 ฝั่งขวากินช่องอะไร สองฝั่งเหมือนหรือต่างกัน

ลองเอง · เลือกช่องให้อัศวินที่มาสาย

ชุดอุ่นเครื่อง เดินตามที่อ่านมาข้างบนได้เลย ลองกดช่องท้ายแถวดูด้วยว่าเกิดอะไรขึ้น

 

กดช่องสีเทาช่องใดช่องหนึ่งเพื่อแทรกอัศวินที่มาสายลงตรงนั้น

ช่องที่ลองแล้วจะติดเลขจำนวนรอบที่ชนะไว้ กดซ้ำได้ไม่จำกัด

แถวสีน้ำเงินคืออัศวินที่ยืนอยู่แล้วพร้อมค่าพลัง ช่องสีเทาคั่นระหว่างพวกเขาคือที่ว่างที่แทรกได้ รายการรอบอยู่เหนือกระดาน ตัวเลข S กับ E นับบนแถว ณ ตอนนั้น

ลองให้ครบทั้งสี่ชุดก่อนนะครับ โดยเฉพาะชุดที่สองกับชุดที่สาม พอเริ่มเดาถูกว่าช่องไหนดีก่อนกด ค่อยไปดูเฉลย

เฉลย ตอนที่ 1 · อะไรที่ไม่เปลี่ยนเมื่อย้ายที่แทรก

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

วิธีตรง ๆ ตกตั้งแต่คูณเลข 99,999 ช่อง คูณ 99,997 รอบ ก็ 9,999,600,003 แล้ว ต้องหาอะไรสักอย่างที่คิดครั้งเดียวแล้วใช้ได้กับทุกช่องพร้อมกัน

จุดเริ่มคือเทียบสองฝั่งของรูปแรก คนที่รอดรอบแรกต่างกัน (ซ้าย 3 ขวา 4) แต่ช่องตั้งต้นที่รอบนั้นยุบรวมเป็นชุดเดียวกัน คือช่อง 1 ถึง 3

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

ก้อน คืออะไร

  • ก้อน คือช่วงของตำแหน่งตั้งต้น (เลข 0 ถึง N-1 ของแถวก่อนรอบแรก) เลขพวกนี้ไม่ขยับระหว่างเกม
  • ทุกคนที่ยังยืนอยู่เป็นตัวแทนของก้อนหนึ่งก้อน ตอนเริ่มเกมก้อนของแต่ละคนคือช่องของตัวเอง
  • รอบที่กิน S ถึง E เชื่อมก้อนของคนเหล่านั้นเป็นก้อนเดียว เรียกว่า [A, B] ผู้ชนะเป็นตัวแทนคนใหม่
  • ตัวอย่างชุดแรก: รอบ 1 (S = 1, E = 3) ยุบช่องตั้งต้น 1 ถึง 3 เป็นก้อน [1, 3] รอบ 2 (S = 0, E = 1) รวมคนที่ถือช่อง 0 กับคนที่ถือ [1, 3] ได้ก้อน [0, 3]

ก้อน [A, B] ของแต่ละรอบเป็นช่วงเดิมเสมอ ไม่ว่าใครชนะหรือแทรกตรงไหน การชนะเปลี่ยนแค่ว่าใครถือก้อน และค่าพลังไม่มีส่วน ลำดับของก้อนคิดครั้งเดียวแล้วใช้กับทั้ง N ช่อง

ช่องตั้งต้น อัศวินเดิม 0 1 2 3 4 K0 = 1 K1 = 0 K2 = 2 K3 = 4 รอบ 1 · ก้อน [1, 3] · ไม่มีใครแรงกว่า 3 · ชนะ รอบ 2 · ก้อน [0, 3] · ไม่มีใครแรงกว่า 3 · ชนะ รอบ 3 · ก้อน [0, 4] · มีคนแรงกว่า 3 อยู่ 1 คน · แพ้
ก้อนของตัวอย่างชุดแรก แถวบนคือช่องตั้งต้น 0 ถึง 4 แถวกลางคืออัศวินเดิมทั้ง 4 คน วาดคร่อมรอยต่อของช่อง เพราะ K[i] จะไปอยู่ช่อง i หรือช่อง i+1 ก็ได้ แล้วแต่ว่าแทรกคนมาสายไว้ตรงไหน แถบล่างคือก้อนที่แต่ละรอบยุบรวม กล่องแดงคือคนที่แรงกว่า 3 ซึ่งเข้ามาอยู่ในก้อนครั้งแรกตอนรอบที่ 3

การหา [A, B] ของแต่ละรอบคือการถามว่า คนที่ตำแหน่ง S ของแถวตอนนี้ถือก้อนที่เริ่มตรงไหน และคนที่ตำแหน่ง E ถือก้อนที่จบตรงไหน โค้ดตอบสองคำถามนี้ด้วยตัวแปรเหล่านี้

  • act: ช่องตั้งต้นไหนยังเป็นหัวก้อน (ช่องซ้ายสุดของก้อนที่ยังมีคนถือ) ตอนเริ่มทุกช่องเป็นหัวก้อน เก็บใน BIT (Fenwick tree ต้นไม้เก็บผลรวมสะสมที่แก้ทีละช่องได้) ค่า 1 คือยังเป็นหัว
  • cnt: จำนวนคนในแถวตอนนั้น ใช้ดูว่าคนที่ E เป็นคนท้ายแถวหรือเปล่า (E + 1 = cnt)
  • A = หัวก้อนลำดับที่ S (นับจาก 0)
  • B = หัวก้อนลำดับที่ E + 1 ลบหนึ่ง ถ้าคนที่ E อยู่ท้ายแถว B คือ N - 1
  • หลังรอบนั้น ลบหัวก้อนลำดับ S + 1 ถึง E ออกจาก act (ก้อนพวกนั้นถูกกลืนเข้า [A, B] แล้ว) และลด cnt ลง E - S

หาหัวก้อนลำดับที่ k ใน BIT

ช่อง t[i] ของ BIT เก็บผลรวมของช่วงที่จบที่ i ยาวเท่ากับบิตต่ำสุดของ i (1, 2, 4, …) เริ่มที่ pos = 0 แล้วลองก้าวยาว 216, 215, … ลงไปถึง 1

ถ้า t[pos + ก้าว] ยังน้อยกว่าจำนวนหัวที่ยังต้องนับ ก็ก้าวไปแล้วหักจำนวนนั้นออก จบแล้ว pos ชี้หัวลำดับที่ k (BIT ในโค้ดนับช่องจาก 1 เลข pos ที่ได้จึงเป็นเลขช่องแบบนับจาก 0 ของหัวนั้นพอดี)

ใช้ log N ก้าว คือจำนวนครั้งที่หาร N ครึ่งหนึ่งจนเหลือ 1 (ราว 17 ที่ N = 99,999)

สร้าง act ด้วยการบวกทีละช่อง N ครั้ง ทุกรอบหาหัวก้อน 2 ครั้ง และการลบหัวก้อนรวมทั้งเกมไม่เกิน N - 1 ครั้ง (ครั้งละหาหนึ่งครั้งกับบวกหนึ่งครั้ง) รวมราว (2C + 3N) log N

เฉลย ตอนที่ 2 · แล้วรอบที่เขาแพ้ล่ะ

ตอนที่ 1 ยังไม่ได้พูดถึงรอบที่คนมาสายแพ้ รอบนั้นเขาออกจากสนาม แต่ผู้ชนะยังยืนอยู่ และช่วง S ถึง E ยังยุบเหลือคนเดียวเหมือนทุกรอบ

แถวยาวเท่ากันทุกรอบไม่ว่าแทรกตรงไหน ก้อนของรอบหลังจากที่เขาแพ้ก็ยังเป็นช่วงเดิม

เรื่องเดียวที่ต้องจัดการ

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

เฉลย ตอนที่ 3 · หนึ่งบิตต่อรอบ แล้วกวาดทีเดียว

แทรกเขาไว้ช่อง p เขาก็ยึดช่องตั้งต้นหมายเลข p เขาได้ลงสนามรอบที่ j เมื่อ p อยู่ในก้อน [A, B] ของรอบนั้น และชนะเมื่อในก้อนไม่มีใครแรงกว่าเขา

แล้วในก้อนมีใครบ้าง ช่องตั้งต้น i ที่มาก่อน p มี K[i] ยืนอยู่ ส่วนช่องที่มาหลัง p มี K[i-1] ยืนอยู่ (ทุกคนข้างหลังถูกเลื่อนไปหนึ่งช่อง)

นับรวมแล้ว คนเดิมในก้อน [A, B] คือ K[A] ถึง K[B-1] พอดี ไม่ว่า p เป็นเท่าไร รูปที่สองวาดกล่องอัศวินคร่อมรอยต่อของช่องเพื่อบอกเรื่องนี้

บิตของรอบที่ j

  • f[i] = 1 ถ้า K[i] > R ไม่งั้น 0
  • pre[i] = f[0] + … + f[i-1] (ไม่รวม f[i]) ผลต่าง pre[B] - pre[A] นับ K[A] ถึง K[B-1] พอดี
  • รอบ j เป็นรอบชนะ เมื่อ pre[B] - pre[A] = 0 ตรวจได้ใน O(1)
  • เงื่อนไขนี้ไม่มี p อยู่ ทุกช่องในก้อนที่ยังไม่แพ้ได้ผลเดียวกัน

ที่เหลือคือกวาดรอบตามลำดับ และดูแลทั้ง N ช่องไปพร้อมกันด้วยตัวแปรสามตัว

  • diff: BIT อีกตัวแบบ "บวกทั้งช่วง ถามทีละจุด" รอบชนะบวก 1 ที่ A และลบ 1 ที่ B + 1 ผลรวมสะสมถึง p เพิ่ม 1 เฉพาะช่อง A ≤ p ≤ B และผลรวมนี้คือคะแนนของช่อง p (ถามครั้งละ log N)
  • nxt: ตัวชี้ "ช่องที่ยังไม่ตายถัดไป" แบบ DSU (disjoint set union โครงสร้างรวมกลุ่ม) รอบแพ้ไล่เฉพาะช่องที่ยังไม่ตายใน [A, B] แล้วชี้ช่องที่ตายไปหาช่องถัดไป
  • find (หาช่องที่ยังไม่ตายถัดไปผ่าน nxt) ย่อทางระหว่างเดิน ครั้งหนึ่งอาจเดินหลายก้าว แต่เฉลี่ยทั้งเกมแล้วต่ำ (amortised)
  • ans: ช่องที่ตายในรอบแพ้ ล็อกคะแนนจาก diff ไว้ตอนนั้น ช่องที่รอดถึงจบอ่านจาก diff ตอนจบ

รอบชนะไม่ต้องแตะช่องที่ตายแล้ว คะแนนของมันถูกล็อกไว้ใน ans ก่อนหน้า แต้มที่บวกทีหลังไม่มีผลกับมัน

แต่ละช่องตายครั้งเดียว รอบแพ้ทั้งเกมล็อกช่องรวมไม่เกิน N ครั้ง บวก find อีกหนึ่งครั้งต่อรอบแพ้ที่หลุดออกนอกก้อน

กวาดตัวอย่างชุดแรก
รอบ 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 ที่ยังไม่ตาย ตกรอบพร้อมกัน ล็อกคะแนนไว้ตรงนั้น
ค่าพลังของอัศวินเดิมคือ 1, 0, 2, 4 และคนมาสายมีพลัง 3 มีคนเดียวที่แรงกว่าเขาคือ 4 ซึ่งนั่งอยู่ปลายแถว ก้อนสองรอบแรกยังไม่ครอบเขา รอบที่ 3 ก้อนกินทั้งแถว บิตพลิกเป็นแพ้
ผลลัพธ์ต่อช่อง
ช่องที่แทรก 01234
จำนวนรอบที่ชนะ 12220
ช่อง 0 อยู่นอกก้อนของรอบแรก ได้แค่รอบที่สอง ช่อง 4 อยู่นอกก้อนสองรอบแรก ลงสนามครั้งเดียวในรอบสุดท้ายแล้วแพ้ ได้ 0 ค่าสูงสุดคือ 2 จาก 3 ช่อง

ชุดแรกมีแค่ 3 รอบ และไม่มีรอบไหนต้องข้ามช่องที่ล็อกแล้ว เครื่องเดินข้างล่างใช้ตัวอย่างชุดที่สองของโจทย์: 10 ช่อง คนมาสายมีพลัง 3 แถวเดิมคือ 6, 2, 9, 8, 1, 5, 4, 0, 7 คนที่แรงกว่า 3 คือ 6, 9, 8, 5, 4, 7

แต่ละขั้นบอกก่อนว่าคนที่ตำแหน่ง S กับ E ของแถวตอนนั้นถือก้อนไหน ก้อนของรอบเริ่มที่ต้นก้อนของคนที่ S และจบที่ท้ายก้อนของคนที่ E

กวาดตัวอย่างชุดที่สอง 7 รอบ

คะแนนของทุกช่องหลังจบแต่ละรอบ
รอบ ก้อน บิต 0123456789
1 [4, 5] ชนะ 0000110000
2 [1, 2] ชนะ 0110110000
3 [0, 2] แพ้ · 1 0110110000
4 [7, 8] ชนะ 0110110110
5 [6, 9] แพ้ · 2 0110110110
6 [3, 9] แพ้ · 4 0110110110
7 [0, 9] แพ้ · 6 0110110110
จบ · · 0110110110

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

ข้อนี้มีสามวิธีที่ไล่จากช้าไปเร็ว โจทย์บน programming.in.th ไม่แบ่ง subtask (ชุดทดสอบย่อยที่ให้คะแนนแยก) การ์ดกับตารางข้างล่างเทียบกันตามขนาด N แทน

วิธีที่ 1

จำลองทุกช่องตรง ๆ

ราว N3 / 2 ก้าว เมื่อ C = N - 2

ทุกช่อง p สร้างแถวใหม่ทุกรอบ ถูกแน่นอน ใช้เป็นตัวตรวจ วัดได้ 0.04 วินาทีที่ N = 500 และ 2.8 วินาทีที่ N = 2,000 (ราว 1.4 × 109 ก้าวต่อวินาที)

งบ 109 ก้าว: ราว N = 1,200 · อัตราที่วัดได้: ราว N = 1,400

วิธีที่ 2

ก้อน + ไล่ทีละช่อง

ราว N2 / 2 + N × C ก้าว

หาก้อนด้วยการเดินแถวตรง ๆ (รอบที่ j เดิน N - j คน รวมราว N2 / 2) ตัดสินบิตด้วย pre แล้วแต่ละช่องไล่ C รอบเองจนเจอรอบแพ้ ยังไม่กวาด ผมไม่ได้เขียนโค้ดของวิธีนี้ ตัวเลขเป็นการนับก้าว

งบ 109 ก้าว: ราว N = 25,800

วิธีเต็ม

ก้อน + บิต + กวาดทีเดียว

ราว (4C + 4N) × log N ≈ 14 ล้านก้าว ที่ N = 99,999

(2C + 3N) log N จากการหาก้อน บวก 2C log N จากการบวกช่วงใน diff และ N log N จากการถามคะแนนช่องละครั้ง วัดได้ไม่เกิน 0.07 วินาที

ทันทุกขนาด

จำนวนก้าวที่ C = N - 2
วิธี N = 500N = 5,000N = 99,999
วิธีที่ 1 จำลองทุกช่อง 6.3 × 10^76.3 × 10^105.0 × 10^14
วิธีที่ 2 ก้อน + ไล่ทีละช่อง 374,7453.7 × 10^71.5 × 10^10
วิธีเต็ม ก้อน + บิต + กวาด 35,910519,8701.4 × 10^7
เขียวคือไม่เกิน 1,000,000,000 ก้าว (งบคร่าว ๆ ของหนึ่งวินาที) แดงคือเกิน log N ของแต่ละคอลัมน์คิดจาก N ของคอลัมน์นั้น

เฉลย ตอนที่ 4 · ทำไมสัญชาตญาณในเกมถึงพาไปผิดทาง

ชุด "มีคนแรงกว่ายืนขวางอยู่" ลงโทษท่าไปยืนข้างคนที่อ่อนที่สุดในแถวเพราะคิดว่าจะชนะง่ายที่สุด ท่านั้นพาไปช่อง 3 ซึ่งได้ 0 รอบ ขณะที่ช่อง 4 ได้ 2 รอบ

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

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

ช่อง 4 อยู่ในก้อนแค่ 0 รอบ ส่วนช่อง 2 อยู่ครบ 3 รอบ ชุดนี้แยก "แพ้หรือไม่แพ้" ออกจาก "ได้ลงสนามหรือไม่ได้ลง"

ชุด "ดีเท่ากันหลายช่อง" มีช่องที่ดีที่สุด 4 ช่อง (ช่อง 3, 4, 5, 6) ได้ 2 รอบเท่ากัน คำตอบที่ต้องพิมพ์คือช่อง 3

โค้ด

ดูโค้ดเต็มขอบเขต
tournament.cpp
// IOI 2012 "Tournament" (programming.in.th task 2040) - full solution, O((N + C) log N)
//
// Idea:
//   1) The merge structure is independent of the strengths and of the insert position.
//      Every surviving knight represents a contiguous block of INITIAL positions 0..N-1,
//      so round j merges current elements S_j..E_j into one block [A_j, B_j] of initial
//      positions.  A Fenwick tree with find-kth gives A_j, B_j in O(log N) each.
//   2) Who wins a round never changes the blocks: even when the late knight loses, the
//      winner stays and S..E still collapses to one person.  So the only thing to handle
//      is to stop counting at his first loss (freeze ans[p] there).
//   3) Inserting him at position p means he occupies initial position p.  He takes part in
//      round j iff p is in [A_j, B_j], and (during the prefix before his first loss) he wins
//      iff no knight stronger than R sits anywhere in [A_j, B_j] other than himself, i.e.
//      iff sum f[A_j .. B_j - 1] == 0 where f[i] = (K[i] > R).  That condition does not
//      depend on p, only on the round -> one bit per round.
//   4) Sweep the rounds in order: a winning round adds +1 to every still-alive p in
//      [A_j, B_j] (range-add / point-query Fenwick), a losing round kills every still-alive
//      p in the range (DSU "next alive" pointer), freezing its answer.  Each p dies once.
#include <bits/stdc++.h>
using namespace std;

namespace fastsol {

struct Fen {
    int n;
    vector<int> t;
    void init(int n_) { n = n_; t.assign(n + 1, 0); }
    void add(int i, int v) { for (++i; i <= n; i += i & -i) t[i] += v; }
    int sum(int i) const { int s = 0; for (++i; i > 0; i -= i & -i) s += t[i]; return s; }
    // 0-indexed k-th set position (assumes it exists)
    int kth(int k) const {
        int pos = 0, rem = k + 1, LOG = 0;
        while ((1 << (LOG + 1)) <= n) ++LOG;
        for (int pw = 1 << LOG; pw > 0; pw >>= 1) {
            if (pos + pw <= n && t[pos + pw] < rem) { pos += pw; rem -= t[pos]; }
        }
        return pos;
    }
};

// returns {best insert position (smallest one achieving the max), max number of wins}
pair<int, int> solve(int N, int C, int R, const vector<int>& K,
                     const vector<pair<int, int>>& rounds,
                     vector<int>* out = nullptr) {
    // ---- 1) block of initial positions merged by each round -------------------
    Fen act;
    act.init(N);
    for (int i = 0; i < N; ++i) act.add(i, 1);
    int cnt = N;
    vector<int> A(C), B(C);
    for (int j = 0; j < C; ++j) {
        int S = rounds[j].first, E = rounds[j].second;
        int a = act.kth(S);
        int b = (E + 1 < cnt) ? act.kth(E + 1) - 1 : N - 1;
        A[j] = a; B[j] = b;
        for (int r = S + 1; r <= E; ++r) act.add(act.kth(S + 1), -1); // absorb the losers
        cnt -= (E - S);
    }

    // ---- 2) per-round win flag ------------------------------------------------
    // pre[i] = number of original knights among K[0..i-1] that are stronger than R
    vector<int> pre(N, 0);                       // K has N-1 entries, so pre has N entries
    for (int i = 0; i + 1 < N; ++i) pre[i + 1] = pre[i] + (K[i] > R ? 1 : 0);
    vector<char> win(C);
    for (int j = 0; j < C; ++j) win[j] = (pre[B[j]] - pre[A[j]] == 0);

    // ---- 3) sweep -------------------------------------------------------------
    Fen diff;                 // range add, point query
    diff.init(N + 1);
    vector<int> nxt(N + 2);   // DSU: next still-alive position >= i
    for (int i = 0; i < N + 2; ++i) nxt[i] = i;
    function<int(int)> find = [&](int x) {
        while (nxt[x] != x) { nxt[x] = nxt[nxt[x]]; x = nxt[x]; }
        return x;
    };
    vector<int> ans(N, -1);

    for (int j = 0; j < C; ++j) {
        int a = A[j], b = B[j];
        if (win[j]) {
            diff.add(a, 1);
            if (b + 1 <= N) diff.add(b + 1, -1);
        } else {
            for (int p = find(a); p <= b; p = find(p + 1)) {
                ans[p] = diff.sum(p);   // frozen at the moment he is knocked out
                nxt[p] = p + 1;         // dead
            }
        }
    }
    for (int p = 0; p < N; ++p) if (ans[p] < 0) ans[p] = diff.sum(p);

    int bestP = 0, bestV = ans[0];
    for (int p = 1; p < N; ++p) if (ans[p] > bestV) { bestV = ans[p]; bestP = p; }
    if (out) *out = ans;
    return {bestP, bestV};
}

} // namespace fastsol

#ifndef STRESS_BUILD
int main() {
    int N, C, R;
    if (scanf("%d %d %d", &N, &C, &R) != 3) return 0;
    vector<int> K(N - 1);
    for (int i = 0; i + 1 < N; ++i) scanf("%d", &K[i]);
    vector<pair<int, int>> rounds(C);
    for (int j = 0; j < C; ++j) scanf("%d %d", &rounds[j].first, &rounds[j].second);
    pair<int, int> r = fastsol::solve(N, C, R, K, rounds);
    printf("%d\n", r.first);
    return 0;
}
#endif
โค้ดส่วนไหนทำขั้นไหน
ส่วนในโค้ดของที่เก็บอธิบายไว้ที่
struct FenBIT พร้อม kth (หาช่องที่ 1 ลำดับที่ k)ตอนที่ 1
1) block of initial positionsact, A, B, cntตอนที่ 1
2) per-round win flagpre, winตอนที่ 3 (บิตของรอบ)
3) sweepdiff, nxt, ansตอนที่ 2 และ 3 (ล็อกคะแนนตอนแพ้ครั้งแรก)
ลูปท้าย solvebestP, bestVกล่องอ่านตัวอย่าง

main ถูกครอบด้วย #ifndef STRESS_BUILD เพื่อให้ตัวรันสเตรสเทสต์ลิงก์ไฟล์นี้กับตัวตรวจเข้าไปด้วยกันได้ ถ้าเอาไปส่งจริงก็คอมไพล์ตรง ๆ

พารามิเตอร์ out มีไว้ให้สเตรสเทสต์ขอคะแนนของทุกช่อง จะได้เทียบกับตัวตรวจได้ทั้งแถว

ดูตัวตรวจที่ใช้สเตรสเทสต์
brute_tournament.cpp
// IOI 2012 "Tournament" - honest brute force oracle.
// For each of the N insert positions, build the explicit line and simulate every round
// literally: scan the range S..E, find the strongest, rebuild the line with only that
// knight kept in the range.  Count a win whenever the late knight is inside the range and
// turns out to be the strongest.  No structure is shared with the fast solution.
#include <bits/stdc++.h>
using namespace std;

namespace brutesol {

pair<int, int> solve(int N, int C, int R, const vector<int>& K,
                     const vector<pair<int, int>>& rounds,
                     vector<int>* out = nullptr) {
    int bestP = -1, bestV = -1;
    if (out) out->assign(N, 0);
    for (int p = 0; p < N; ++p) {
        vector<int> line;
        line.reserve(N);
        for (int i = 0; i < p; ++i) line.push_back(K[i]);
        line.push_back(R);
        for (int i = p; i + 1 < N; ++i) line.push_back(K[i]);

        int wins = 0;
        for (int j = 0; j < C; ++j) {
            int S = rounds[j].first, E = rounds[j].second;
            if (E >= (int)line.size()) { fprintf(stderr, "invalid round\n"); exit(1); }
            int mi = S;
            bool lateHere = false;
            for (int i = S; i <= E; ++i) {
                if (line[i] > line[mi]) mi = i;
                if (line[i] == R) lateHere = true;
            }
            if (lateHere && line[mi] == R) ++wins;
            vector<int> nl;
            nl.reserve(line.size());
            for (int i = 0; i < S; ++i) nl.push_back(line[i]);
            nl.push_back(line[mi]);
            for (int i = E + 1; i < (int)line.size(); ++i) nl.push_back(line[i]);
            line.swap(nl);
        }
        if (out) (*out)[p] = wins;
        if (wins > bestV) { bestV = wins; bestP = p; }
    }
    return {bestP, bestV};
}

} // namespace brutesol

#ifndef STRESS_BUILD
int main() {
    int N, C, R;
    if (scanf("%d %d %d", &N, &C, &R) != 3) return 0;
    vector<int> K(N - 1);
    for (int i = 0; i + 1 < N; ++i) scanf("%d", &K[i]);
    vector<pair<int, int>> rounds(C);
    for (int j = 0; j < C; ++j) scanf("%d %d", &rounds[j].first, &rounds[j].second);
    pair<int, int> r = brutesol::solve(N, C, R, K, rounds);
    printf("%d\n", r.first);
    return 0;
}
#endif

ตัวตรวจสร้างแถวจริงทีละช่องที่แทรก แล้วเดินทุกรอบตามตัวหนังสือของโจทย์ กวาดหาคนแรงสุดในช่วง แล้วสร้างแถวใหม่ที่เหลือแค่คนนั้น

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

ตรวจอะไรไปแล้วบ้าง

  • ตัวอย่างทั้งสองชุดในโจทย์: โค้ดเต็มและตัวตรวจตอบ 1 ทั้งคู่
  • ไล่ครบทุกกรณี: N = 4 ถึง 7 (ที่ N = 3 ไม่มีจำนวนรอบที่ถูกกติกา) ทุกค่า R ทุกการเรียงของคนที่เหลือ ทุกลำดับรอบ รวม 22,092,864 เคส เทียบคะแนนทุกช่องกับตัวตรวจ ตรงกันหมด
  • สุ่มเพิ่ม 2,896 เคส N ไม่เกิน 32 วน R เป็นแรงสุด อ่อนสุด แรงรองสุด และสุ่ม ตรงกันหมด
  • หน้านี้เองตอนบิลด์: เทียบวิธีที่สอนกับการจำลองแถวตรง ๆ อีก 3,034 เคสสุ่ม ถ้าต่างแม้เคสเดียว หน้านี้ไม่ถูกสร้าง
  • เวลาและหน่วยความจำ: ชุดหนัก 13 ชุดที่ N = 99,999 C = 99,997 ใช้ 0.03 ถึง 0.07 วินาที สูงสุดราว 7.4 MB เทียบลิมิต 1 วินาที 64 MB

ทั้งหมดนี้คือการทดสอบ ไม่ได้พิสูจน์ และยังไม่ได้ส่งเกรดเดอร์จริง

ทดสอบตัวเองและสรุป

ในตัวอย่างชุดแรก ถ้าแทรกที่ช่อง 4 ทำไมได้ 0 รอบ

ก้อนของสองรอบแรกคือ [1, 3] กับ [0, 3] ไม่ครอบช่อง 4 เขาไม่ได้ลงสนาม รอบที่ 3 ก้อนคือ [0, 4] มีคนแรงกว่า 3 คือ 4 บิตเป็นแพ้ เขาตกรอบไปพร้อม 0 แต้ม

ทำไมคนเดิมในก้อน [A, B] คือ K[A] ถึง K[B-1] เสมอ ไม่ว่าแทรกตรงไหน

สนใจแค่ตอนที่ช่อง p ของเขาอยู่ในก้อน (ถ้าไม่อยู่ เขาไม่ได้ลงสนาม) ช่อง A ถึง p - 1 มี K[A] ถึง K[p-1] ยืนอยู่

ช่อง p + 1 ถึง B มีคนที่ถูกเลื่อนไปหนึ่งช่อง คือ K[p] ถึง K[B-1] ต่อกันได้ K[A] ถึง K[B-1] ทุกค่า p

ถ้ารอบแพ้วนทุกช่องในก้อนตรง ๆ โดยไม่ใช้ตัวชี้ข้ามช่องที่ตาย จะเกิดอะไรขึ้น

ลองแถวที่ทุกรอบเรียก 0 1 และคนมาสายอ่อนสุด ก้อนของรอบที่ j คือ [0, j] และทุกรอบเป็นรอบแพ้ เพราะทุกก้อนมี K[0] ที่แรงกว่าเขา

วนตรง ๆ จะแตะช่องรวม 4,999,850,000 ครั้งที่ C = 99,997 ส่วนตัวชี้ฆ่าแต่ละช่องครั้งเดียว ไม่เกิน 99,999 ครั้ง

สรุปทั้งข้อ

แหล่งที่มา

  1. โจทย์ Tournament บน programming.in.th ข้อ 2040 programming.in.th/tasks/2040 (สืบค้น 11 กันยายน 2026)
  2. ต้นฉบับคือ Jousting tournament ของ International Olympiad in Informatics 2012 ที่เมือง Sirmione และ Montichiari ประเทศอิตาลี เป็นโจทย์ของวันแข่งที่สอง ioi2012.org/competition/tasks
  3. ฉบับที่ยังส่งโค้ดไปตรวจได้อยู่ ใช้ชื่อ IOI12_tournament oj.uz/problem/view/IOI12_tournament
  4. เฉลยทางการของ IOI 2012 ซึ่งไล่ระดับวิธีจาก O(N3) ไป O(N2) และ O(N log N) ioinformatics.org/files/ioi2012solutions.pdf
  5. กติกา "ถ้าดีเท่ากันให้ตอบช่องเลขน้อยสุด" ในหน้านี้ ผมอนุมานจากตัวอย่างสองชุดของ programming.in.th โจทย์ไม่ได้เขียนไว้ ต้นฉบับของ IOI รับตำแหน่งที่ดีที่สุดช่องไหนก็ได้