programming.in.th · ข้อ 2035
จำลองโรงจอดรถที่รถอาจล้นจนต้องต่อคิว ตัวอย่างในโจทย์ไม่มีรถรอเลยสักคัน บทนี้จึงพาไปดูวันที่รถล้น และข้อสังเกตข้อเดียวที่ทำให้กติกาสองข้อไม่มีวันชนกัน
โรงจอดรถแห่งหนึ่งมีที่จอด N ช่อง เลขที่ 1 ถึง N ทุกเช้าเปิดมาว่างเปล่า
แต่ละช่องมีอัตราค่าบริการของตัวเองเป็นดอลลาร์ต่อกิโลกรัม ค่าจอดของรถหนึ่งคันคือ
น้ำหนักรถคูณราคาของช่องที่มันได้จอด จอดนานแค่ไหนก็จ่ายเท่าเดิม
กติกาของคนเฝ้าโรงจอดมีสองข้อ รถที่มาถึงตอนมีช่องว่าง ให้จอดช่องว่างที่เลขน้อยที่สุด
ถ้าเต็มทุกช่อง ให้รอที่ทางเข้า และรถที่รอหลายคันจะต่อคิวตามลำดับที่มาถึง
พอมีช่องว่าง รถหัวคิวได้เข้าก่อน วันนี้เจ้าของรู้ล่วงหน้าว่ามีรถ M คัน
และรู้ลำดับการมาถึงกับการออกทั้งหมด งานของเราคือบอกว่าวันนี้เขาได้เงินรวมเท่าไร
อินพุต / ขอบเขต / เอาต์พุต
N กับ M
ต่อด้วย N บรรทัด บรรทัดที่ s คือราคาของช่อง s
ต่อด้วย M บรรทัด บรรทัดที่ k คือน้ำหนักของรถคัน k
แล้วปิดด้วย 2M บรรทัดเรียงตามเวลา เลขบวก i คือรถคัน i มาถึง
เลขลบ -i คือรถคัน i ออก
1 ≤ N ≤ 100, 1 ≤ M ≤ 2,000,
ราคาแต่ละช่อง 1 ถึง 100 และน้ำหนักรถ 1 ถึง 10,000 กิโลกรัม
เวลา 1 วินาที หน่วยความจำ 64 เมกะไบต์
| Input | Output |
|---|---|
| 3 4 2 3 5 200 100 300 800 3 2 -3 1 4 -4 -2 -1 | 5300 |
อ่านตัวอย่างนี้ยังไง
บรรทัดแรกบอกว่ามี 3 ช่อง รถ 4 คัน อีก 3 บรรทัดถัดมาคือราคาช่อง 1 ถึง 3 (2, 3, 5) อีก 4 บรรทัดคือน้ำหนักรถคัน 1 ถึง 4 (200, 100, 300, 800) ที่เหลือ 8 บรรทัดคือเหตุการณ์ตามเวลา ระวังว่าเลขในส่วนนี้คือเลขประจำรถ ไม่ใช่เลขช่อง บรรทัดแรกของส่วนนี้เป็น 3 จึงแปลว่ารถคัน 3 มาถึงเป็นคันแรก ไม่ใช่รถคันแรกไปจอดช่อง 3
คำตอบมีหน่วยเป็นดอลลาร์ และนับเฉพาะตอนรถได้จอด ในตัวอย่างนี้ได้จอด 4 ครั้ง คือ คัน 3 ช่อง 1 ได้ 300 คูณ 2 เท่ากับ 600 คัน 2 ช่อง 2 ได้ 100 คูณ 3 เท่ากับ 300 คัน 1 ช่อง 1 ได้ 200 คูณ 2 เท่ากับ 400 คัน 4 ช่อง 3 ได้ 800 คูณ 5 เท่ากับ 4,000 รวมเป็น 5,300 สังเกตว่ารถคัน 1 ได้ช่อง 1 ซึ่งถูกที่สุด เพราะคัน 3 เพิ่งออกไปพอดี และไม่มีรถคันไหนต้องรอเลย ตัวอย่างนี้จึงอยู่ในชุด 40 คะแนน
ใบ้
ขอบเขตเล็กมาก ช่องไม่เกิน 100 รถไม่เกิน 2,000 คัน ข้อนี้จึงไม่ได้วัดความเร็ว มันวัดว่าเราจำลองกติกาครบทุกกรณีไหม ส่วนที่ตัวอย่างในโจทย์ไม่ได้โชว์ให้ดูเลยคือวันที่รถล้น
ลองนึกภาพตอนที่มีรถรออยู่หน้าทางเข้า แล้วรถคันหนึ่งขับออก ตอนนั้นโรงจอดมีช่องว่างกี่ช่อง และคุณต้องค้นหาช่องให้หัวคิวจริงหรือเปล่า
วันนี้ไม่มีใครต้องรอเลย ลองจัดให้ถูกทุกคันก่อน แล้วดูว่ารายได้ออกมา 5,300 ไหม
กดช่องที่รถคันนี้ควรได้จอด หรือกดปุ่มด้านล่างถ้ามันต้องรอหรือกำลังจะออก
รายได้ 0 ดอลลาร์
เงินเก็บตอนรถได้จอด คือน้ำหนักคูณราคาของช่องนั้น ไม่ได้เก็บตอนรถออก
ลองให้ครบทั้งสามวันก่อนนะครับ โดยเฉพาะวันที่รถล้น ช่วงที่มีรถออกตอนคิวยังไม่ว่าง คือช่วงที่เฉลยข้างล่างพูดถึง
ที่มาของแนวคิดนี้
ข้อนี้เป็นการจำลองล้วน ๆ ขอบเขตก็เล็ก ผมจึงไม่ได้ลองท่าอื่นแล้วทิ้ง สิ่งเดียวที่ทำให้ผมหยุดคิดคือ กติกาสองข้อดูเหมือนจะชนกันได้ ข้อหนึ่งบอกให้จอดช่องเลขน้อยสุด อีกข้อบอกให้หัวคิวเข้าก่อน ถ้าตอนรถออกมีช่องว่างพร้อมกันหลายช่อง หัวคิวควรได้ช่องไหน และต้องสแกนหาใหม่ไหม
คำตอบมาจากการถามย้อนว่าทำไมถึงมีคนรอได้ รถจะต่อคิวก็ต่อเมื่อตอนมันมาถึงไม่มีช่องว่างเลย และตราบใดที่คิวยังไม่หมด ช่องไหนว่างขึ้นมาก็ถูกหัวคิวเอาไปทันที ช่องว่างจึงไม่มีทางสะสมได้ระหว่างที่มีคนรอ ตอนรถคันหนึ่งออกในวันที่มีคิว โรงจอดจึงมีช่องว่างอยู่ช่องเดียว คือช่องที่รถคันนั้นเพิ่งปล่อยคืน กติกาสองข้อไม่มีวันชนกัน
ผมไม่อยากให้โค้ดยืนอยู่บนเหตุผลที่เขียนในหัวอย่างเดียว ตัวตรวจที่ใช้เทียบจึงจงใจไม่เชื่อข้อนี้ มันไล่หาช่องว่างเลขน้อยสุดกับรถที่รอนานสุดใหม่ทุกครั้ง และหน้านี้ก็เช็กตอนบิลด์ด้วยว่า ทุกครั้งที่หัวคิวได้เข้า ช่องว่างในโรงมีหนึ่งช่องพอดี ข้อสังเกตที่เอาไปใช้กับข้ออื่นได้คือ เวลากติกาสองข้อดูจะขัดกัน ให้ถามว่าสถานะแบบไหนที่ทำให้ทั้งสองข้อต้องทำงานพร้อมกัน บ่อยครั้งมันเกิดไม่ได้เลย
ผลคือตอนรถออก เราไม่ต้องหาอะไรเลย ปล่อยช่องคืน แล้วถ้ามีคิว ให้หัวคิวเข้าช่องเดิมนั้น
งานค้นหาเหลือแค่ตอนรถมาถึง ซึ่งสแกนช่อง 1 ถึง N หาช่องแรกที่ว่าง
// รถออก: ปล่อยช่องคืน แล้วถ้ามีคนรอ ให้หัวคิวเข้าช่องนี้เลย ไม่ต้องสแกนหา
int s = at[-x]; who[s] = 0;
if(!wait.empty()){ park(wait.front(), s); wait.pop(); }
ของที่ต้องจำระหว่างทางมีสามอย่าง คือช่องแต่ละช่องมีรถคันไหนจอดอยู่ รถแต่ละคันจอดช่องไหน
(ไว้ใช้ตอนเห็นเลข -i ซึ่งบอกแค่เลขรถ ไม่ได้บอกเลขช่อง) และคิวของรถที่รอ
คิวต้องเป็นแบบมาก่อนได้ก่อน เพราะโจทย์บอกให้หัวคิวคือคันที่มาถึงก่อนที่สุด
รูปนี้โชว์อีกอย่างที่คนมักพลาดตอนอ่านโจทย์ คือค่าจอดของคันที่รอคิวคิดจากช่องที่มันได้จริง คัน 4 ได้ช่อง 1 ที่แพงที่สุดเพราะจังหวะล้วน ๆ รถที่ออกเป็นคันที่สองของวันคือคัน 3 ซึ่งจอดช่อง 1 อยู่พอดี ถ้าสลับให้คัน 3 ออกก่อนคัน 1 หัวคิวคือคัน 2 จะได้ช่อง 1 ไปแทน คัน 4 เหลือช่อง 2 ค่าจอดของมันจะเหลือ 4,000 และรายได้ทั้งวันจะเหลือ 11,700
2 ช่อง ราคา 5 และ 2 รถ 4 คัน หนัก 100, 500, 1,000, 2,000 กิโลกรัม
| เหตุการณ์ | ช่อง 1 | ช่อง 2 | คิว | ได้เพิ่ม | รวม |
|---|---|---|---|---|---|
| คัน 3 มา | คัน 3 | ว่าง | · | 5,000 | 5,000 |
| คัน 1 มา | คัน 3 | คัน 1 | · | 200 | 5,200 |
| คัน 2 มา | คัน 3 | คัน 1 | 2 | · | 5,200 |
| คัน 4 มา | คัน 3 | คัน 1 | 2, 4 | · | 5,200 |
| คัน 1 ออก | คัน 3 | คัน 2 | 4 | 1,000 | 6,200 |
| คัน 3 ออก | คัน 4 | คัน 2 | · | 10,000 | 16,200 |
| คัน 2 ออก | คัน 4 | ว่าง | · | · | 16,200 |
| คัน 4 ออก | ว่าง | ว่าง | · | · | 16,200 |
ยอดสุดท้าย 16,200 ตรงกับตัวตรวจที่ไล่จับคู่ใหม่หลังทุกเหตุการณ์ และทั้ง 4 ครั้งที่มีรถได้จอด ไม่มีครั้งไหนที่ต้องเลือกระหว่างช่องว่างหลายช่องพร้อมกับมีคนรอ
เวลาที่วัดได้บนเครื่องผม ชุดที่หนักที่สุดคือ 100 ช่อง รถ 2,000 คันที่มาถึงหมดก่อนแล้วค่อยทยอยออก ทำให้มีคนรอคิวยาวเกือบสองพันคัน โค้ดเต็มใช้เวลา 0.007 วินาที จากลิมิต 1 วินาที และตอบ 2,000,000,000 ตรงกับตัวตรวจ
ข้อจำลองส่วนใหญ่ไม่ได้ยากที่อัลกอริทึม มันยากที่กรณีที่ตัวอย่างไม่ได้โชว์ ตัวอย่างของข้อนี้ไม่มีรถรอเลยสักคัน ใครที่เขียนแค่ให้ผ่านตัวอย่างจะได้ 40 คะแนนแล้วหยุด นิสัยที่ช่วยได้คือเขียนวันทดสอบของตัวเองที่บังคับให้ทุกกติกาในโจทย์ได้ทำงานอย่างน้อยหนึ่งครั้ง
อีกท่าคือเวลากติกาสองข้อดูจะชนกัน ให้ถามว่าสถานะที่ทำให้มันชนกันเกิดขึ้นได้จริงไหม ที่นี่มันเกิดไม่ได้ และพอรู้แบบนั้น โค้ดตอนรถออกก็เหลือบรรทัดเดียว
| ทาง | จำนวนครั้งที่ตรวจช่อง | เวลาชุดหนักสุด |
|---|---|---|
| ตัวตรวจ ไล่จับคู่ซ้ำหลังทุกเหตุการณ์ | ไม่เกินราว 800,000,000 ครั้ง | 0.24 วินาที |
| คิวหนึ่งอัน สแกนหาช่องว่างตอนรถมาถึง | ไม่เกิน 200,000 ครั้ง | 0.007 วินาที |
จำลองตรง ๆ ด้วยอาเรย์สองตัวกับคิวหนึ่งอัน รถมาถึงก็หาช่องว่างแรก รถออกในวันที่มีคิวก็ยกช่องนั้นให้หัวคิวเลย เพราะระหว่างที่มีคนรอ ช่องว่างไม่มีทางเกินหนึ่งช่อง
ในหน้านี้