programming.in.th · ข้อ 2033
ค่าของแต่ละข้อรู้ได้ก็ต่อเมื่ออ่านผลของทุกคนครบ ข้อนี้จึงต้องเดินตารางสองรอบ และ Philip ถามแค่อันดับของตัวเอง จึงไม่ต้องเรียงทั้งตาราง แค่นับคนที่อยู่เหนือเขาด้วยการเทียบสามชั้น
การแข่งขันเขียนโปรแกรมระดับท้องถิ่นของเมือง Plovdiv (อ่านว่า "พลอฟดิฟ" เมืองในบัลแกเรีย) หรือ POI
มีกติกาแปลกอยู่ข้อหนึ่ง มีผู้เข้าแข่ง N คน โจทย์ T ข้อ
แต่ละข้อมีชุดทดสอบชุดเดียว ใครทำได้ก็ได้ทั้งข้อ ใครทำไม่ได้ก็ไม่ได้อะไรเลย ไม่มีคะแนนบางส่วน
ส่วนที่แปลกคือค่าของแต่ละข้อยังไม่มีใครรู้ระหว่างแข่ง มันถูกตั้งหลังแข่งจบ ให้เท่ากับจำนวนผู้เข้าแข่งที่ทำข้อนั้นไม่ได้ ข้อที่คนส่วนใหญ่ตกม้าตายจึงมีค่ามาก ส่วนข้อที่ทุกคนทำได้มีค่าเป็นศูนย์ คะแนนของแต่ละคนคือผลรวมค่าของข้อที่เขาทำได้
ตารางอันดับเรียงคะแนนจากมากไปน้อย ถ้าคะแนนเสมอ คนที่ทำได้มากข้อกว่าอยู่ก่อน
ถ้ายังเสมออีก คนที่เลขประจำตัวน้อยกว่าอยู่ก่อน
Philip มีเลขประจำตัว P เขางงกับกติกานี้ และอยากรู้แค่สองอย่าง คือตัวเองได้กี่คะแนน กับอยู่อันดับที่เท่าไร
อินพุต / ขอบเขต / เอาต์พุต
N T P
จากนั้นอีก N บรรทัด บรรทัดที่ k มีเลข 0 หรือ 1 อยู่ T ตัว
ตัวที่ j เป็น 1 เมื่อคนที่ k ทำข้อ j ได้
1 ≤ N, T ≤ 2,000, 1 ≤ P ≤ N
เวลา 2 วินาที หน่วยความจำ 64 เมกะไบต์
N)| Input | Output |
|---|---|
| 5 3 2 0 0 1 1 1 0 1 0 0 1 1 0 1 1 0 | 3 2 |
ตัวอย่างนี้มีห้าคน สามข้อ Philip คือคนที่ 2 แถวที่สองของตาราง (1 1 0) จึงบอกว่าเขาทำข้อไหนได้บ้าง เอาต์พุตตัวแรกคือคะแนน ตัวที่สองคืออันดับ
ใบ้
ลองถามว่าตอนอ่านแถวของคนที่ 1 จบ เรารู้คะแนนของเขาหรือยัง ค่าของข้อที่ 1 ขึ้นกับใครบ้าง แล้วเราต้องอ่านไปถึงไหนถึงจะรู้
อีกเรื่องหนึ่ง โจทย์ไม่ได้ขอให้พิมพ์ตารางอันดับทั้งตาราง ถามแค่อันดับของคนเดียว ถ้าอยากรู้ว่าตัวเองอยู่ที่เท่าไร ต้องรู้อะไรเกี่ยวกับคนอื่นบ้าง
ห้าคนสามข้อจากตัวอย่างในโจทย์ Philip คือคนที่ 2 เริ่มจากคิดค่าของแต่ละข้อก่อน แล้วค่อยเรียงคน
| คนที่ | ข้อ 1 | ข้อ 2 | ข้อ 3 | อันดับ |
|---|---|---|---|---|
| 0 | 0 | 1 | ||
| 1 | 1 | 0 | ||
| 1 | 0 | 0 | ||
| 1 | 1 | 0 | ||
| 1 | 1 | 0 | ||
| ค่าข้อ | 1? | 2? | 4? |
| คนที่ | ข้อ 1 | ข้อ 2 | ข้อ 3 | ข้อ 4 | อันดับ |
|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | ||
| 1 | 0 | 0 | 0 | ||
| 0 | 1 | 0 | 1 | ||
| 0 | 1 | 1 | 1 | ||
| 1 | 0 | 0 | 0 | ||
| 0 | 1 | 1 | 1 | ||
| ค่าข้อ | 4? | 2? | 3? | 2? |
| คนที่ | ข้อ 1 | ข้อ 2 | ข้อ 3 | ข้อ 4 | ข้อ 5 | อันดับ |
|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 1 | 0 | ||
| 1 | 1 | 0 | 1 | 0 | ||
| 0 | 1 | 0 | 1 | 0 | ||
| 0 | 1 | 1 | 1 | 0 | ||
| 1 | 0 | 1 | 0 | 0 | ||
| 0 | 1 | 0 | 0 | 0 | ||
| ค่าข้อ | 3? | 1? | 4? | 2? | 6? |
ยังไม่ได้วางใคร
กดเลขประจำตัวทีละคน เริ่มจากคนที่ควรอยู่อันดับ 1 ลงไปจนครบ
ค่าของแต่ละข้อยังซ่อนอยู่ ลองคิดเองก่อน ถ้าติดค่อยกดเปิดค่าข้อ
ลองจัดให้ครบทั้งสามชุดก่อนนะครับ โดยเฉพาะชุดสุดท้ายที่ Philip ทำได้น้อยข้อกว่าเพื่อน พอเห็นว่าเขาไปอยู่ตรงไหนแล้ว ค่อยเปิดเฉลย
ที่มาของแนวคิดนี้
ข้อนี้ไม่มีอัลกอริทึมให้เดา แรงกดดันเดียวมาจากลำดับของอินพุต อินพุตมาทีละคน แต่ค่าของข้อมาจากทั้งแนวตั้ง คือต้องรู้ว่าทุกคนทำข้อนั้นได้หรือไม่ ตอนอ่านแถวของคนที่ 1 จบ ผมจึงยังบอกคะแนนเขาไม่ได้เลยสักนิด
ทางที่ตรงที่สุดคือทุกช่องที่ใครทำได้ ให้วิ่งลงไปนับคนที่ทำข้อนั้นไม่ได้ใหม่ ผมคิดราคาไว้ก่อนเขียน ที่ตาราง 2,000 คูณ 2,000 ช่องที่เป็น 1 อาจมีถึงสี่ล้านช่อง แต่ละช่องวิ่งลงอีกสองพันแถว รวมแปดพันล้านครั้ง ซึ่งเกินสองวินาทีไปไกล ทั้งที่ทุกช่องในแนวตั้งเดียวกันได้คำตอบเดียวกันหมด การนับซ้ำจึงเป็นงานเปล่า
บทเรียนที่ยกไปข้ออื่นได้คือ เมื่อค่าที่ต้องใช้ขึ้นกับข้อมูลที่ยังอ่านไม่ถึง ให้อ่านเก็บให้หมดก่อน แล้วคำนวณเป็นสองรอบ รอบแรกหาค่ารวมที่ทุกคนใช้ร่วมกัน รอบสองค่อยใช้มัน
รอบแรก อ่านตารางทั้งหมดเก็บไว้ ระหว่างอ่านก็นับไปพร้อมกันว่าแต่ละข้อมีกี่คนที่ได้ 0 นั่นคือค่าของข้อนั้นพอดี รอบสอง เดินตารางอีกครั้ง ช่องไหนเป็น 1 ก็บวกค่าของข้อนั้นเข้าคะแนนของเจ้าของแถว และนับจำนวนข้อที่ทำได้ไว้ด้วย เพราะกติกาตอนเสมอต้องใช้
สังเกตคนที่ 1 ในภาพ เขาทำได้แค่ข้อเดียว แต่ข้อนั้นมีคนทำไม่ได้ 4 คนจาก 5 คะแนนของเขาจึงชนะทุกคนที่ทำได้สองข้อ ในเกมชุดสุดท้ายข้างบน Philip ก็ได้อานิสงส์แบบเดียวกัน จัดตามจำนวนข้อเขาจะอยู่อันดับ 5 แต่อันดับจริงคือ 2
ที่มาของแนวคิดนี้
พอได้คะแนนครบทุกคน ท่าแรกที่มือจะเขียนคือเรียงทั้งตารางด้วยตัวเปรียบเทียบสามชั้น แล้วหาว่า Philip อยู่ช่องไหน
ท่านี้ถูกและเร็วพอด้วย ที่ N สองพันคือการเทียบราวสองหมื่นครั้ง ผมใช้มันเป็นตัวตรวจอยู่แล้ว
แต่คำถามของโจทย์ชี้ทางที่ง่ายกว่านั้น อันดับของ Philip ก็คือหนึ่งบวกจำนวนคนที่ต้องอยู่เหนือเขา คนแต่ละคนจะอยู่เหนือหรือใต้ Philip ตัดสินได้ด้วยการเทียบกับเขาคนเดียว ไม่ต้องสนว่าคนอื่นเรียงกันเองยังไง จึงเดินรอบเดียว เทียบทีละคน แล้วนับ
บทเรียนที่ยกไปข้ออื่นได้คือ ถ้าโจทย์ถามตำแหน่งของของชิ้นเดียว ให้นับของที่ชนะมันแทนการเรียงทั้งกอง
กติกาจัดอันดับเขียนเป็นการเทียบสามชั้นได้ตรงตัว ดูคะแนนก่อน ถ้าต่างกันก็จบตรงนั้น ถ้าเสมอค่อยลงไปดูจำนวนข้อที่ทำได้ ถ้ายังเสมอค่อยดูเลขประจำตัว ชั้นที่สามนี่เองที่ทำให้ไม่มีใครเสมอกันจริง ๆ ทุกคนมีตำแหน่งของตัวเองตำแหน่งเดียว
// คนที่ i ต้องอยู่เหนือ Philip (คนที่ me) ไหม
bool ahead(int i, int me){
if(score[i] != score[me]) return score[i] > score[me]; // ชั้นที่ 1 คะแนน
if(solved[i] != solved[me]) return solved[i] > solved[me]; // ชั้นที่ 2 จำนวนข้อ
return i < me; // ชั้นที่ 3 เลขประจำตัว
} จุดที่พลาดง่ายคือชั้นที่สองกับสาม ถ้านับเฉพาะคนที่คะแนนมากกว่า Philip คนที่คะแนนเท่าเขาจะหายไปจากการนับทั้งหมด ในเกมชุดที่สอง Philip เสมอคะแนนกับอีก 2 คน ท่าที่นับแค่คะแนนจะให้อันดับ 4 ขณะที่อันดับจริงคือ 6
เทียบทุกคนกับ Philip (คนที่ 2) ในตัวอย่างของโจทย์ ค่าข้อคือ 1, 2, 4
| คนที่ | ทำได้ข้อ | คะแนน | จำนวนข้อ | เทียบกับ Philip | อยู่เหนือ (สะสม) |
|---|---|---|---|---|---|
| 1 | 3 | 4 | 1 | คะแนนมากกว่า | 1 |
| 2 | 1, 2 | 3 | 2 | Philip เอง | 1 |
| 3 | 1 | 1 | 1 | คะแนนน้อยกว่า | 1 |
| 4 | 1, 2 | 3 | 2 | เสมอทั้งคู่ เลขมากกว่า | 1 |
| 5 | 1, 2 | 3 | 2 | เสมอทั้งคู่ เลขมากกว่า | 1 |
มีคนอยู่เหนือ Philip 1 คน อันดับจึงเป็น 2 และคะแนน 3 ตรงกับเอาต์พุตของตัวอย่าง คนที่คะแนนเสมอเขาในตัวอย่างนี้ทำได้จำนวนข้อเท่ากันด้วย จึงไปตัดสินกันที่เลขประจำตัว
เวลาที่วัดได้บนเครื่องผม ตาราง 2,000 x 2,000 ที่สุ่มเลข 1 ครึ่งหนึ่ง ใช้ 0.69 วินาที
และตารางที่ทุกคนทำได้ทุกข้อ (ทุกคนได้ศูนย์ อันดับตัดสินด้วยเลขประจำตัวล้วน) ใช้ 0.68 วินาที จากลิมิต 2 วินาที
เวลาเกือบทั้งหมดคือการอ่านเลขสี่ล้านตัวด้วย scanf ส่วนการคิดจริงแทบไม่กินเวลา
ข้อนี้มีสองจังหวะ จังหวะแรกคือสังเกตว่าค่าที่ต้องใช้มาจากอีกแกนหนึ่งของข้อมูล อินพุตมาเป็นแถว แต่ค่าของข้อมาจากแนวตั้ง จึงต้องเก็บทั้งตารางแล้วเดินสองรอบ จังหวะที่สองคือถ้าถามตำแหน่งของคนเดียว ให้นับคนที่ชนะเขา แทนการเรียงทุกคน
| ทาง | งานคร่าว ๆ ที่ N = T = 2,000 | สูตร |
|---|---|---|
| ทุกช่องที่ทำได้ นับคนที่ทำข้อนั้นไม่ได้ใหม่ | 8,000,000,000 | N x T x N |
| นับค่าของทุกข้อก่อน แล้วค่อยรวมคะแนน | 8,000,000 | สองรอบ รอบละ N x T |
| หาอันดับด้วยการเรียงทุกคน | ราว 22,000 | N x log N การเทียบ |
| หาอันดับด้วยการนับคนที่อยู่เหนือ Philip | 1,999 | N - 1 การเทียบ |
อ่านทั้งตาราง นับเลข 0 ในแต่ละแนวตั้งเป็นค่าของข้อ รวมคะแนนอีกรอบ แล้วนับคนที่อยู่เหนือ Philip ด้วยการเทียบสามชั้น คะแนน จำนวนข้อ เลขประจำตัว อันดับคือจำนวนนั้นบวกหนึ่ง
ในหน้านี้