วงแหวนเว็บ

neizod's speculation

insufficient data for meaningful answer

Code Jam 2022: ภาวะพองตัวที่ควบคุมได้

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

แน่นอนว่ารอบนี้ไม่เต็มเพราะข้อสุดท้ายนั้นยากจัด 5555TT55 ส่วนข้อแรกก็ง่ายเกินไปแบบว่าน่าจะเอาไว้ปลอบใจให้มีคะแนนติดไม้ติดมือกลับไปบ้าง ก็เหลือแต่ข้อตรงกลางที่ยากพอดีๆ นี่แหละ

จริงๆ ชื่อโจทย์ภาษาอังกฤษนี่แอบฮา(ไม่ออก)อยู่ เพราะเล่นกับคำว่า inflation ที่แปลว่า “การเป่าลม” หรือ “เงินเฟ้อ” ก็ได้ ซึ่งน่าจะล้อกับสถานการณ์ปัจจุบันที่ทั่วโลกเผชิญวิกฤติเศรษฐกิจนี้กันถ้วนหน้า ([1], [2], [3]) จนทำให้คนรุ่นก่อร่างสร้างตัวจำนวนมากเลือกที่จะยอมแพ้ในระบบทุนนิยมแล้วแค่ใช้ชีวิตไปวันๆ ปล่อยให้อนาคตเป็นเรื่องในอนาคตไม่เก็บมาคิดใส่ใจกันเลยทีเดียว ([4], [5], [6])

แต่โอเคแหละ โจทย์ไม่ได้เอาเรื่องจริงมาเขียนเป็นโจทย์ให้เราอ่านแล้วหดหู่เล่น เพราะเปลี่ยนไปเล่าเรื่องผ่านร้านรับเติมลมแห่งหนึ่งที่มีลูกค้าต่อคิวกันเยอะมาก จนต้องหาวิธีจัดลำดับการเติมลมสิ่งของต่างๆ ของลูกค้าแต่ละคนให้เกิดประสิทธิภาพสูงสุด ในขณะที่รักษาสิทธิลูกค้าแต่ละคนโดยไม่ให้ลัดคิวกัน

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

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

ลองนึกตัวอย่างที่ลูกค้าคนแรกมีของสองชิ้นที่ต้องการแรงดัน $\lbrace20,50,60\rbrace$ ปาสคาล ส่วนลูกค้าคนถัดมาต้องการแรงดัน $\lbrace50,60\rbrace$ ปาสคาล ถ้าเราปิดจ๊อปลูกค้าคนแรกที่แรงดัน $20$ ปาสคาลนั้นไม่ดีแน่ เพราะเราต้องกดปุ่มปรับแรงดันอย่างน้อยสามสิบครั้งเพื่อที่จะเริ่มให้บริการลูกค้าคนถัดไปได้ แต่ถ้าเราปิดจ๊อปลูกค้าคนแรกที่ $50$ ปาสคาลแทน เราก็จะให้บริการลูกค้าคนถัดไปได้ทันทีเลย

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

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

ซึ่งนี่น่าจะเป็นความสวยงามของโจทย์ข้อนี้ที่ให้เราทำกำหนดการพลวัตรสองแบบล้อกันไปโดยจัดวางสับหว่างฟันปลานี่แหละ

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

from collections import namedtuple

Cell = namedtuple('Cell', 'presses pascal')

def make_init(pascal, cell):
    presses = cell.presses + abs(pascal-cell.pascal)
    return Cell(presses, pascal)

def min_init(dp_out, x):
    return min(make_init(x, c) for c in dp_out)

def make_target(pascal, cell, lo, hi):
    presses = cell.presses - abs(pascal-cell.pascal) + 2*abs(hi-lo)
    return Cell(presses, pascal)

def min_target(dp_in, x, lo, hi):
    return min(make_target(x, c, lo, hi) for c in dp_in)

def minimum_presses(ps):
    dp_out = [Cell(presses=0, pascal=0)]
    for person_products in ps:
        lo, *_, hi = sorted(person_products)
        dp_in = [min_init(dp_out, x) for x in [lo, hi]]
        dp_out = [min_target(dp_in, x, lo, hi) for x in [lo, hi]]
    return min(c.presses for c in dp_out)

for t in range(int(input())):
    n, _ = [int(x) for x in input().split()]
    ps = [[int(x) for x in input().split()] for _ in range(n)]
    answer = minimum_presses(ps)
    print(f'Case #{t+1}: {answer}')

neizod

author