programming.in.th · ข้อ 2040
อัศวินคนหนึ่งมาสาย เลือกช่องในแถวให้เขาชนะได้มากรอบที่สุดก่อนตกรอบ ช่องให้ลองมีเกือบแสนช่อง รอบก็เกือบแสนรอบ จำลองทุกช่องตรง ๆ ไม่ทันแน่ ระวังด้วยว่าโจทย์ให้ตอบเป็นเลขช่อง
ทหาร N คนยืนเรียงเป็นเส้นตรง นับตำแหน่งจาก 0 ถึง N-1
ทุกคนมีค่าพลังไม่ซ้ำกัน ตั้งแต่ 0 (อ่อนสุด) ถึง N-1 (แรงสุด)
เจ้าของงานเรียกประลองทั้งหมด C รอบ แต่ละรอบเป็นแบบนี้
S กับ ES ถึง E ของแถวตอนนั้น ต้องสู้กัน
ตอนนี้มีคนยืนอยู่แล้ว N-1 คน เหลืออัศวินหนึ่งคนที่มาสาย พลังของเขาคือ R
เราเลือกได้ว่าจะให้เขาไปแทรกตรงไหนของแถว
เป้าหมายคือให้เขาชนะให้ได้มากรอบที่สุด รอบที่เขาไม่ได้ถูกเรียกลงสนามไม่นับ และพอเขาแพ้ครั้งแรกเขาก็ออกจากสนามเหมือนคนอื่น
อินพุต / ขอบเขต / เอาต์พุต
N C R
ต่อด้วย N-1 บรรทัดของค่าพลัง แล้วปิดด้วย C บรรทัด บรรทัดละ S กับ E เรียงตามลำดับรอบ
K[i]: ค่าพลังบรรทัดที่ i (นับจาก 0) คือคนที่ยืนตำแหน่ง i ตั้งแต่แรก โดย i = 0 ถึง N-2 1 < N < 100000, 1 < C < N-1,
เวลา 1 วินาที หน่วยความจำ 64 เมกะไบต์ และในแต่ละรอบ 0 ≤ S < E
โดย E ไม่เกินตำแหน่งสุดท้ายของแถวตอนนั้น
N สูงสุด 99,999 และ C สูงสุด 99,997 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 |
อ่านตัวอย่างนี้ยังไง
ชุดแรกอ่านทีละบรรทัดได้แบบนี้
S E ของแต่ละรอบ คือ 1 3 / 0 1 / 0 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 รับช่องที่ดีที่สุดช่องไหนก็ได้
ใบ้
วิธีตรง ๆ คือลองแทรกครบทุกช่องแล้วจำลองแถว แต่ช่องมี N ช่อง ช่องละ C รอบ
ที่ขอบเขตใหญ่สุดก็ 9,999,600,003 คู่ (ช่อง, รอบ) แล้ว ยังไม่นับการกวาดช่วงในแต่ละรอบ
กลับไปดูรูปข้างบน รอบที่หนึ่งกินคนกลุ่มไหนของแถวตั้งต้น (เลขบนสุด) ฝั่งซ้ายกินช่อง 1 ถึง 3 ฝั่งขวากินช่องอะไร สองฝั่งเหมือนหรือต่างกัน
ชุดอุ่นเครื่อง เดินตามที่อ่านมาข้างบนได้เลย ลองกดช่องท้ายแถวดูด้วยว่าเกิดอะไรขึ้น
กดช่องสีเทาช่องใดช่องหนึ่งเพื่อแทรกอัศวินที่มาสายลงตรงนั้น
ช่องที่ลองแล้วจะติดเลขจำนวนรอบที่ชนะไว้ กดซ้ำได้ไม่จำกัด
แถวสีน้ำเงินคืออัศวินที่ยืนอยู่แล้วพร้อมค่าพลัง ช่องสีเทาคั่นระหว่างพวกเขาคือที่ว่างที่แทรกได้
รายการรอบอยู่เหนือกระดาน ตัวเลข S กับ E นับบนแถว ณ ตอนนั้น
ลองให้ครบทั้งสี่ชุดก่อนนะครับ โดยเฉพาะชุดที่สองกับชุดที่สาม พอเริ่มเดาถูกว่าช่องไหนดีก่อนกด ค่อยไปดูเฉลย
ที่มาของแนวคิดนี้
วิธีตรง ๆ ตกตั้งแต่คูณเลข 99,999 ช่อง คูณ 99,997 รอบ ก็ 9,999,600,003 แล้ว ต้องหาอะไรสักอย่างที่คิดครั้งเดียวแล้วใช้ได้กับทุกช่องพร้อมกัน
จุดเริ่มคือเทียบสองฝั่งของรูปแรก คนที่รอดรอบแรกต่างกัน (ซ้าย 3 ขวา 4) แต่ช่องตั้งต้นที่รอบนั้นยุบรวมเป็นชุดเดียวกัน คือช่อง 1 ถึง 3
บทเรียนที่ใช้กับข้ออื่นได้: เวลาต้องตอบคำถามเดิมซ้ำกับอินพุตที่ต่างกันนิดเดียว ให้หาส่วนที่เป็นของกลางแล้วคิดมันครั้งเดียว
ก้อน คืออะไร
0 ถึง N-1 ของแถวก่อนรอบแรก) เลขพวกนี้ไม่ขยับระหว่างเกมS ถึง E เชื่อมก้อนของคนเหล่านั้นเป็นก้อนเดียว เรียกว่า [A, B] ผู้ชนะเป็นตัวแทนคนใหม่
ก้อน [A, B] ของแต่ละรอบเป็นช่วงเดิมเสมอ ไม่ว่าใครชนะหรือแทรกตรงไหน
การชนะเปลี่ยนแค่ว่าใครถือก้อน และค่าพลังไม่มีส่วน ลำดับของก้อนคิดครั้งเดียวแล้วใช้กับทั้ง N ช่อง
การหา [A, B] ของแต่ละรอบคือการถามว่า คนที่ตำแหน่ง S ของแถวตอนนี้ถือก้อนที่เริ่มตรงไหน
และคนที่ตำแหน่ง E ถือก้อนที่จบตรงไหน โค้ดตอบสองคำถามนี้ด้วยตัวแปรเหล่านี้
act: ช่องตั้งต้นไหนยังเป็นหัวก้อน (ช่องซ้ายสุดของก้อนที่ยังมีคนถือ) ตอนเริ่มทุกช่องเป็นหัวก้อน
เก็บใน BIT (Fenwick tree ต้นไม้เก็บผลรวมสะสมที่แก้ทีละช่องได้) ค่า 1 คือยังเป็นหัว
cnt: จำนวนคนในแถวตอนนั้น ใช้ดูว่าคนที่ E เป็นคนท้ายแถวหรือเปล่า (E + 1 = cnt)A = หัวก้อนลำดับที่ S (นับจาก 0)B = หัวก้อนลำดับที่ E + 1 ลบหนึ่ง ถ้าคนที่ E อยู่ท้ายแถว B คือ N - 1S + 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
ตอนที่ 1 ยังไม่ได้พูดถึงรอบที่คนมาสายแพ้ รอบนั้นเขาออกจากสนาม แต่ผู้ชนะยังยืนอยู่
และช่วง S ถึง E ยังยุบเหลือคนเดียวเหมือนทุกรอบ
แถวยาวเท่ากันทุกรอบไม่ว่าแทรกตรงไหน ก้อนของรอบหลังจากที่เขาแพ้ก็ยังเป็นช่วงเดิม
เรื่องเดียวที่ต้องจัดการ
p คือจำนวนรอบชนะ นับจนถึงรอบแรกที่เขาแพ้แล้วหยุดp ต้องไม่นับให้ช่องนี้อีกans ตอนแพ้ครั้งแรก (ตอนที่ 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 ไม่งั้น 0pre[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 ที่ยังไม่ตาย ตกรอบพร้อมกัน ล็อกคะแนนไว้ตรงนั้น |
| ช่องที่แทรก | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| จำนวนรอบที่ชนะ | 1 | 2 | 2 | 2 | 0 |
ชุดแรกมีแค่ 3 รอบ และไม่มีรอบไหนต้องข้ามช่องที่ล็อกแล้ว เครื่องเดินข้างล่างใช้ตัวอย่างชุดที่สองของโจทย์: 10 ช่อง คนมาสายมีพลัง 3 แถวเดิมคือ 6, 2, 9, 8, 1, 5, 4, 0, 7 คนที่แรงกว่า 3 คือ 6, 9, 8, 5, 4, 7
แต่ละขั้นบอกก่อนว่าคนที่ตำแหน่ง S กับ E ของแถวตอนนั้นถือก้อนไหน
ก้อนของรอบเริ่มที่ต้นก้อนของคนที่ S และจบที่ท้ายก้อนของคนที่ E
กวาดตัวอย่างชุดที่สอง 7 รอบ
| รอบ | ก้อน | บิต | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | [4, 5] | ชนะ | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
| 2 | [1, 2] | ชนะ | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
| 3 | [0, 2] | แพ้ · 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
| 4 | [7, 8] | ชนะ | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| 5 | [6, 9] | แพ้ · 2 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| 6 | [3, 9] | แพ้ · 4 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| 7 | [0, 9] | แพ้ · 6 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| จบ | · | · | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
หัวคอลัมน์ 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 วินาที
ทันทุกขนาด
| วิธี | N = 500 | N = 5,000 | N = 99,999 |
|---|---|---|---|
| วิธีที่ 1 จำลองทุกช่อง | 6.3 × 10^7 | 6.3 × 10^10 | 5.0 × 10^14 |
| วิธีที่ 2 ก้อน + ไล่ทีละช่อง | 374,745 | 3.7 × 10^7 | 1.5 × 10^10 |
| วิธีเต็ม ก้อน + บิต + กวาด | 35,910 | 519,870 | 1.4 × 10^7 |
ชุด "มีคนแรงกว่ายืนขวางอยู่" ลงโทษท่าไปยืนข้างคนที่อ่อนที่สุดในแถวเพราะคิดว่าจะชนะง่ายที่สุด ท่านั้นพาไปช่อง 3 ซึ่งได้ 0 รอบ ขณะที่ช่อง 4 ได้ 2 รอบ
เหตุผลอ่านจากก้อนได้ตรง ๆ รอบหนึ่งกินทั้งก้อน ไม่ได้กินแค่เพื่อนบ้านสองข้าง การยืนข้างคนอ่อนไม่ช่วย สิ่งที่ต้องดูคือก้อนที่ครอบช่องนั้นมีคนแรงกว่าเขาหรือเปล่า
ชุด "คนมาสายแรงที่สุดในสนาม" เป็นอีกมุม คนมาสายมีพลัง 6 แรงที่สุดในสนาม บิตทุกรอบเป็นชนะ คำถามเหลือแค่ว่าช่องไหนอยู่ในก้อนมากรอบที่สุด
ช่อง 4 อยู่ในก้อนแค่ 0 รอบ ส่วนช่อง 2 อยู่ครบ 3 รอบ ชุดนี้แยก "แพ้หรือไม่แพ้" ออกจาก "ได้ลงสนามหรือไม่ได้ลง"
ชุด "ดีเท่ากันหลายช่อง" มีช่องที่ดีที่สุด 4 ช่อง (ช่อง 3, 4, 5, 6) ได้ 2 รอบเท่ากัน คำตอบที่ต้องพิมพ์คือช่อง 3
ตรวจอะไรไปแล้วบ้าง
N = 4 ถึง 7 (ที่ N = 3 ไม่มีจำนวนรอบที่ถูกกติกา) ทุกค่า R
ทุกการเรียงของคนที่เหลือ ทุกลำดับรอบ รวม 22,092,864 เคส เทียบคะแนนทุกช่องกับตัวตรวจ ตรงกันหมด
N ไม่เกิน 32 วน R เป็นแรงสุด อ่อนสุด แรงรองสุด และสุ่ม ตรงกันหมด
N = 99,999 C = 99,997
ใช้ 0.03 ถึง 0.07 วินาที สูงสุดราว 7.4 MB เทียบลิมิต 1 วินาที 64 MB
ทั้งหมดนี้คือการทดสอบ ไม่ได้พิสูจน์ และยังไม่ได้ส่งเกรดเดอร์จริง
สรุปทั้งข้อ
preในหน้านี้