programming.in.th · ข้อ 2037

แอร์เมสส่งจดหมาย: หลังส่งแต่ละฉบับ ตำแหน่งเหลือเลขที่ต้องจำแค่ตัวเดียว

ส่งจดหมายสองหมื่นฉบับตามลำดับ แค่ยืนบนถนนเส้นเดียวกับร้านก็ส่งได้ บทนี้ยุบตำแหน่งบนตารางสี่ล้านจุดเหลือสองเส้น แล้วแสดงว่าบนแต่ละเส้นมีจุดที่ควรไปยืนแค่จุดเดียว

★★★☆☆ dpstate space อ่าน 11 นาที 11 กันยายน 2026

โจทย์ · ส่งจดหมายให้เทพทุกองค์ตามลำดับ ด้วยระยะเดินน้อยที่สุด

เมืองของเทพเจ้ากรีกวางถนนเป็นตาราง ทุกจำนวนเต็ม Z มีถนนแนวนอนที่ y = Z และถนนแนวตั้งที่ x = Z คู่พิกัดจำนวนเต็มแต่ละคู่จึงเป็นสี่แยกหนึ่งแห่ง วันที่อากาศร้อน เทพแต่ละองค์ไปนั่งร้านกาแฟตามสี่แยกต่าง ๆ

เทพแอร์เมส (เทพผู้ส่งสาร) ต้องเดินไปตามถนนเพื่อส่งจดหมายลับให้ครบ N ฉบับ ฉบับละหนึ่งองค์ และต้องส่งตามลำดับที่กำหนดเท่านั้น เขาเริ่มที่ (0, 0) กติกาที่ทำให้ข้อนี้น่าสนใจคือ จะส่งจดหมายให้เทพที่ร้าน (X, Y) แอร์เมสไม่ต้องเดินไปถึงร้าน แค่ยืนอยู่บนถนนเส้นเดียวกันก็พอ คือยืนที่ (X, Z) หรือ (Z, Y) สำหรับจำนวนเต็ม Z ใดก็ได้ ใครจะเห็นจดหมายระหว่างทางก็ไม่เป็นไร ส่งครบแล้วแอร์เมสก็หายตัวไป

ถามว่าระยะทางเดินรวมที่น้อยที่สุดคือเท่าไร

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

EXAMPLE
InputOutput
5
8 3
7 -7
8 1
-2 1
6 -5
11

ระยะทางนับตามถนน เดินได้แค่แนวนอนกับแนวตั้ง จาก (0, 0) ไป (3, 2) จึงเป็น 5 ช่วงถนน ตัวอย่างนี้ฉบับแรกอยู่ที่ร้าน (8, 3) แอร์เมสอาจยืนที่ (0, 3) หรือ (8, 0) ก็ส่งได้แล้ว คำตอบ 11 คือระยะรวมของทั้งห้าฉบับ

ใบ้

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

ลองดูว่าหลังส่งฉบับที่ i เสร็จ เรารู้อะไรเกี่ยวกับตำแหน่งของเขาแน่ ๆ บ้าง แล้วจากจุดนั้น จะไปยืนบนเส้นของฉบับถัดไปตรงไหนดี

ลองเอง · พาแอร์เมสส่งให้ครบด้วยระยะน้อยที่สุด

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

 

คลิกจุดบนตารางเพื่อเดินไปยืนตรงนั้น

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

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

เฉลย ตอนที่ 1 · หลังส่งแต่ละฉบับ ตำแหน่งเหลือเลขที่ต้องจำแค่ตัวเดียว

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

ผมเริ่มจากสถานะที่ตรงที่สุดก่อน คือ "ส่งไปแล้วกี่ฉบับ และยืนอยู่ที่ (x, y) ไหน" ตัวเลขฆ่ามันทันที พิกัดแต่ละแกนมี 2,001 ค่า สองแกนรวมกันราวสี่ล้านจุด คูณจำนวนฉบับอีกสองหมื่นเป็นแปดหมื่นล้านสถานะ ส่วนหน่วยความจำ 16 เมกะไบต์เก็บ int ได้แค่ราวสี่ล้านตัว แม้แต่ชั้นเดียวของตารางนี้ก็เกือบเต็มแล้ว

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

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

เรียกถนนสองเส้นที่ผ่านร้านของฉบับที่ i ว่าเส้นแนวนอน y = Yi กับเส้นแนวตั้ง x = Xi หลังส่งฉบับที่ i แอร์เมสยืนอยู่บนเส้นใดเส้นหนึ่งในสองเส้นนี้เสมอ สถานะจึงเขียนได้ด้วยของสองอย่าง

  • row[i][c] คือระยะน้อยที่สุด ถ้าหลังส่งฉบับที่ i เขายืนบนเส้นแนวนอน ที่ x = c
  • col[i][c] คือระยะน้อยที่สุด ถ้าหลังส่งฉบับที่ i เขายืนบนเส้นแนวตั้ง ที่ y = c

จุดเริ่ม (0, 0) นับเป็นฉบับที่ 0 ที่ร้านอยู่ตรงนั้นพอดี แอร์เมสยืนบนทั้งสองเส้นของมันพร้อมกัน จึงเริ่มด้วย row[0][0] = col[0][0] = 0 ถ้าร้านของฉบับแรกบังเอิญอยู่แนวเดียวกับ (0, 0) ก็ส่งได้เลยโดยไม่ต้องเดิน ซึ่งสูตรข้างล่างจะจัดการให้เอง

คำถามที่ยังค้างคือ จากจุดบนเส้นของฉบับที่ i-1 จะไปยืนตรงไหนบนเส้นของฉบับที่ i มีสองเส้นให้เลือก และบนแต่ละเส้นมีสองพันกว่าจุด ตอนที่ 2 ลองทุกจุดก่อน ตอนที่ 3 จะตัดเหลือจุดเดียวต่อเส้น

เฉลย ตอนที่ 2 · ชุดย่อย N < 80: ลองไปยืนทุกจุดบนเส้นใหม่

ทางที่ตรงไปตรงมาที่สุด แล้วตีราคา

เมื่อสถานะเหลือ 2 × 2,001 ช่องต่อฉบับแล้ว ทางที่ไม่ต้องคิดอะไรเพิ่มคือ จากทุกช่อง ลองไปยืนทุกจุดบนสองเส้นของฉบับถัดไป ระยะก็คือระยะตามถนนจากตำแหน่งจริงของช่องนั้นไปจุดนั้น ผมเขียนมันเป็นโค้ดจริงไว้ใช้เป็นตัวเทียบ และมันคือทางที่ผ่านชุดย่อย 50% ได้

ราคาต่อฉบับคือ 2 × 2,001 ช่องต้นทาง คูณ 2 × 2,001 จุดปลายทาง ราวสิบหกล้านครั้ง วัดจริงที่ N เท่ากับ 79 ใช้ 0.37 วินาที ที่ 800 ใช้ 3.8 วินาที เวลาโตตาม N เป็นเส้นตรง พอไปถึงสองหมื่นฉบับจะเกินลิมิต 3 วินาทีไปหลายสิบเท่า สิ่งที่ต้องหาต่อจึงชัดมาก คือในสองพันกว่าจุดบนเส้นใหม่ จุดไหนที่ควรค่าแก่การไปยืนจริง ๆ

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

เฉลย ตอนที่ 3 · บนแต่ละเส้น จุดที่ควรไปยืนมีจุดเดียว

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

คำถามจากตอนที่ 2 คือจุดไหนบนเส้นใหม่ที่ควรค่าแก่การไป ผมลองวาดดูหนึ่งกรณี แอร์เมสยืนที่ P อยากไปยืนที่จุด Q บนเส้นแนวตั้งของฉบับถัดไป ระยะตามถนนจาก P ไป Q แยกได้เป็นสองก้อนเสมอ คือก้อนซ้ายขวากับก้อนขึ้นลง ก้อนซ้ายขวาต้องจ่ายแน่ ๆ เพื่อไปถึงเส้นนั้น ส่วนก้อนขึ้นลงเป็นแค่การขยับไปตามเส้น

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

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

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

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

อ่านสูตรนี้โดยจำไว้ว่า c ในช่องปลายทางคือพิกัด x ที่แอร์เมสยืนหลังส่งฉบับที่ i ส่วนดัชนีของช่องต้นทางคือพิกัดอีกแกนที่เขายืนหลังส่งฉบับก่อนหน้า ทางเข้าช่องปลายทางมีสองทาง

  • ทางตรง ก่อนหน้านี้เขายืนบนเส้นแนวนอนของฉบับก่อนอยู่แล้ว ที่ x = c เดินขึ้นหรือลงตรง ๆ จนชนเส้นแนวนอนใหม่ พิกัด x ไม่ขยับ จึงลงช่อง c เดิมพอดี ระยะที่บวกคือระยะห่างระหว่างสองเส้นแนวนอน ซึ่งเท่ากันทุกช่อง ทางนี้ใช้ได้ทุกช่องที่ฉบับก่อนมีค่าอยู่
  • ทางเลี้ยว ก่อนหน้านี้เขายืนบนเส้นแนวตั้งของฉบับก่อน ซึ่งมี x = Xi-1 ตายตัว ที่ความสูง c' สักค่า เดินขึ้นหรือลงจนชนเส้นแนวนอนใหม่ ระยะ |c' - Yi| และยืนที่ x = Xi-1 ช่องปลายทางของทางนี้จึงเป็นช่องเดียว คือ c = Xi-1 และเลือก c' ที่ดีที่สุดจากทุกช่องของฝั่งแนวตั้ง

ผลที่ตามมาคือแต่ละฉบับเพิ่มช่องใหม่ได้แค่หนึ่งช่องต่อฝั่ง หลังฉบับที่ i แต่ละฝั่งจึงมีช่องที่มีค่าไม่เกิน i + 1 ช่อง (ตัวอย่างในโจทย์มีช่องรวมทั้งสองฝั่ง 2, 2, 4, 6, 7, 8 ตามลำดับ) ส่วนสูตรฝั่ง col ได้จากการสลับ x กับ y ทั้งหมด

สรุปทางเข้าช่อง
ช่องปลายทางมาจากระยะที่บวกเงื่อนไข
row[i][c]row[i-1][c]|Y[i-1] - Y[i]|ทุก c
row[i][c]col[i-1][c'] ทุก c'|c' - Y[i]|เฉพาะ c = X[i-1]
col[i][c]col[i-1][c]|X[i-1] - X[i]|ทุก c
col[i][c]row[i-1][c'] ทุก c'|c' - X[i]|เฉพาะ c = Y[i-1]
คำตอบคือค่าน้อยที่สุดของทุกช่องหลังฉบับสุดท้าย ไม่ว่าจะอยู่ฝั่งไหน เพราะส่งครบแล้วแอร์เมสก็หายตัวไปจากตรงนั้นเลย
ท่าที่ดูเข้าท่า แต่ผิด: เดินเข้าหาเส้นที่ใกล้กว่าเสมอ

ในเมื่อแต่ละก้าวมีแค่สองทาง ก็น่าลองเลือกทางที่สั้นกว่าทุกครั้ง ท่านี้แพ้ตั้งแต่ตัวอย่างในโจทย์ ได้ระยะรวม 15 ขณะที่คำตอบคือ 11 ส่วนชุดสี่ฉบับในเกมข้างบนแพ้ขาดกว่านั้น ท่านี้ได้ 16 ขณะที่ทำได้ 6

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

เดินตารางให้ดูหนึ่งรอบ

ตัวอย่างในโจทย์ ช่องที่มีค่าของแต่ละฝั่ง เขียนเป็น พิกัด · ระยะ

ช่องที่มีค่าหลังส่งแต่ละฉบับ
ฉบับร้าน ฝั่งแนวนอน (x · ระยะ)ฝั่งแนวตั้ง (y · ระยะ)ดีสุด
0 (0, 0) x=0 · 0 y=0 · 0 0
1 (8, 3) x=0 · 3 y=0 · 8 3
2 (7, -7) x=0 · 13, x=8 · 15 y=0 · 9, y=3 · 10 9
3 (8, 1) x=0 · 21, x=7 · 10, x=8 · 23 y=-7 · 15, y=0 · 10, y=3 · 11 10
4 (-2, 1) x=0 · 21, x=7 · 10, x=8 · 11 y=-7 · 25, y=0 · 20, y=1 · 19, y=3 · 21 10
5 (6, -5) x=-2 · 25, x=0 · 27, x=7 · 16, x=8 · 17 y=-7 · 33, y=0 · 28, y=1 · 11, y=3 · 29 11
-3-2-10123456789 -8-7-6-5-4-3-2-101234 1 2 3 4 5 ส่ง 1 ส่ง 2 ส่ง 3,4 ส่ง 5 เริ่ม
เส้นทางที่ย้อนได้จากช่องที่ดีที่สุด ยาว 11 พอดีกับคำตอบ ทุกช่วงเป็นเส้นตรงเส้นเดียวตามที่ตอนที่ 3 บอก จุดวงแหวนคือจุดที่ยืนส่ง ฉบับที่ 4 ส่งได้โดยไม่ต้องขยับ เพราะจุดที่ยืนอยู่แนวเดียวกับร้านถัดไปอยู่แล้ว

โค้ด

ดูโค้ดเต็ม
hermes.cpp
#include <bits/stdc++.h>
using namespace std;
const int OFF = 1000, W = 2001, INF = 1e9;
// row[c] = ระยะน้อยสุด ถ้าตอนนี้ยืนบนถนนแนวนอนของฉบับล่าสุด ที่ x = c - OFF
// col[c] = ระยะน้อยสุด ถ้าตอนนี้ยืนบนถนนแนวตั้งของฉบับล่าสุด ที่ y = c - OFF
int row[W], col[W];
int main(){
    int n;
    if(scanf("%d", &n) != 1) return 0;
    fill(row, row + W, INF); fill(col, col + W, INF);
    row[OFF] = col[OFF] = 0;    // จุดเริ่ม (0,0) นับเป็นฉบับที่ 0 ยืนอยู่บนทั้งสองเส้นของมัน
    int px = 0, py = 0;           // พิกัดของฉบับก่อนหน้า
    for(int i = 0; i < n; i++){
        int x, y; scanf("%d %d", &x, &y);
        // ทางเลี้ยว หาไว้ก่อนจากแถวเก่า
        // toRow: จากถนนแนวตั้งเก่า เดินขึ้นลงไปชนถนนแนวนอนใหม่ y
        // toCol: จากถนนแนวนอนเก่า เดินซ้ายขวาไปชนถนนแนวตั้งใหม่ x
        int toRow = INF, toCol = INF;
        for(int c = 0; c < W; c++){
            if(col[c] < INF) toRow = min(toRow, col[c] + abs(c - OFF - y));
            if(row[c] < INF) toCol = min(toCol, row[c] + abs(c - OFF - x));
        }
        // ทางตรง อยู่ฝั่งเดิม เดินขนานไปเส้นใหม่ พิกัดอีกแกนไม่เปลี่ยน ช่องจึงเป็นช่องเดิม
        int dy = abs(py - y), dx = abs(px - x);
        for(int c = 0; c < W; c++){
            if(row[c] < INF) row[c] += dy;
            if(col[c] < INF) col[c] += dx;
        }
        // ทางเลี้ยวลงได้ช่องเดียวต่อฝั่ง: ถนนแนวนอนใหม่ที่ x = px กับถนนแนวตั้งใหม่ที่ y = py
        row[px + OFF] = min(row[px + OFF], toRow);
        col[py + OFF] = min(col[py + OFF], toCol);
        px = x; py = y;
    }
    int ans = INF;
    for(int c = 0; c < W; c++) ans = min({ans, row[c], col[c]});
    printf("%d\n", ans);
}

ตารางเก็บแค่ชั้นล่าสุด ชั้นละสองแถว แถวละ 2,001 ช่อง รวมราวสิบหกกิโลไบต์ หน่วยความจำ 16 เมกะไบต์จึงเหลือเฟือ จุดที่ต้องระวังคือลำดับการอัปเดต ทางเลี้ยวต้องหาจากแถวเก่าให้เสร็จก่อน แล้วค่อยบวกทางตรงลงแถว ถ้าสลับกัน ทางเลี้ยวจะไปอ่านค่าที่บวกไปแล้ว ระยะรวมมากที่สุดราวสองหมื่นฉบับคูณสี่พัน คือแปดสิบล้าน ยังอยู่ใน int

ดูท่าลองทุกจุด (ชุดย่อย N < 80)
hermes_sub.cpp
#include <bits/stdc++.h>
using namespace std;
const int OFF = 1000, W = 2001, INF = 1e9;
// ชุดย่อย N < 80: ยังไม่เชื่อว่าเดินไปจุดที่ใกล้ที่สุดพอ
// จากทุกสถานะ ลองไปยืนทุกจุดบนสองเส้นของฉบับถัดไป
int row[W], col[W], nrow[W], ncol[W];
int main(){
    int n;
    if(scanf("%d", &n) != 1) return 0;
    fill(row, row + W, INF); fill(col, col + W, INF);
    row[OFF] = col[OFF] = 0;
    int px = 0, py = 0;
    for(int i = 0; i < n; i++){
        int x, y; scanf("%d %d", &x, &y);
        fill(nrow, nrow + W, INF); fill(ncol, ncol + W, INF);
        for(int c = 0; c < W; c++){
            for(int s = 0; s < 2; s++){
                int d = s == 0 ? row[c] : col[c];
                if(d >= INF) continue;
                int cx = s == 0 ? c - OFF : px;   // ตำแหน่งจริงของสถานะนี้
                int cy = s == 0 ? py : c - OFF;
                for(int z = 0; z < W; z++){
                    nrow[z] = min(nrow[z], d + abs(cx - (z - OFF)) + abs(cy - y));   // ไปยืน (z, y)
                    ncol[z] = min(ncol[z], d + abs(cx - x) + abs(cy - (z - OFF)));   // ไปยืน (x, z)
                }
            }
        }
        copy(nrow, nrow + W, row); copy(ncol, ncol + W, col);
        px = x; py = y;
    }
    int ans = INF;
    for(int c = 0; c < W; c++) ans = min({ans, row[c], col[c]});
    printf("%d\n", ans);
}

ท่านี้ได้คะแนนชุดย่อย 50% ที่ N < 80 ส่วนที่เหลือจะหมดเวลา มันไม่ได้ใช้ข้ออ้างเรื่องจุดที่ใกล้ที่สุดเลย จึงเป็นตัวเทียบที่ตรงประเด็นที่สุดของตอนที่ 3

ดูตัวตรวจที่ใช้เทียบ
brute_hermes.cpp
// ตัวตรวจ เดินทีละก้าวบนตารางจริง ส่งจดหมายได้ฟรีเมื่อยืนแนวเดียวกับฉบับถัดไป
// ไม่ได้ใช้ข้ออ้างเรื่องถนนสองเส้นเลย สถานะคือ (ส่งไปแล้วกี่ฉบับ, x, y)
#include <bits/stdc++.h>
using namespace std;
int main(){
    int n; scanf("%d", &n);
    vector<int> X(n), Y(n);
    int lo = 0, hi = 0;
    for(int i = 0; i < n; i++){
        scanf("%d %d", &X[i], &Y[i]);
        lo = min({lo, X[i], Y[i]}); hi = max({hi, X[i], Y[i]});
    }
    lo -= 2; hi += 2;                       // เผื่อขอบให้เดินอ้อมนอกกรอบได้
    int S = hi - lo + 1;
    auto id = [&](int k, int x, int y){ return (k * S + (x - lo)) * S + (y - lo); };
    vector<int> dist((n + 1) * S * S, INT_MAX);
    deque<array<int, 3>> dq;                // BFS แบบน้ำหนัก 0 กับ 1
    dist[id(0, 0, 0)] = 0; dq.push_back({0, 0, 0});
    int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
    while(!dq.empty()){
        auto [k, x, y] = dq.front(); dq.pop_front();
        int d = dist[id(k, x, y)];
        if(k == n){ printf("%d\n", d); return 0; }
        if(x == X[k] || y == Y[k]){         // ส่งฉบับที่ k ตรงนี้ ไม่เสียระยะ
            int t = id(k + 1, x, y);
            if(dist[t] > d){ dist[t] = d; dq.push_front({k + 1, x, y}); }
        }
        for(int r = 0; r < 4; r++){
            int nx = x + dx[r], ny = y + dy[r];
            if(nx < lo || nx > hi || ny < lo || ny > hi) continue;
            int t = id(k, nx, ny);
            if(dist[t] > d + 1){ dist[t] = d + 1; dq.push_back({k, nx, ny}); }
        }
    }
}

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

เวลาที่วัดได้บนเครื่องผม ท่าเต็มกับจดหมาย 19,999 ฉบับที่พิกัดสุ่มเต็มช่วงใช้ 0.05 วินาที รวมเวลาอ่านอินพุตแล้ว จากลิมิต 3 วินาที

ท่าที่ติดมือกลับไป

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

บิลค่าใช้จ่าย
ทางขอบเขตที่มันไหวงานคร่าว ๆ ที่ N = 20,000
เดินทีละก้าวบนตารางทั้งแผ่น (ตัวตรวจ)พิกัดแคบ ๆN × 2,001² ช่อง คือราว 80,080,020,000 ช่อง
สองเส้น ลองทุกจุดบนเส้นใหม่ (ชุดย่อย)N ต่ำกว่า 80N × 2 × 2,001 × 2 × 2,001 คือราว 320,320,080,000 ครั้ง
สองเส้น เดินไปจุดที่ใกล้ที่สุด (ท่าเต็ม)N ถึง 20,000N × 2 × 2,001 × 2 คือราว 160,080,000 ครั้ง
สองแถวล่างนับหน่วยเดียวกัน คือจำนวนครั้งที่คำนวณระยะจากช่องหนึ่งไปจุดหนึ่ง แถวบนนับจำนวนช่องบนตารางเต็ม ซึ่งแต่ละช่องแตะเพื่อนบ้านอีกสี่ครั้ง จึงแพงกว่าที่เห็นอีก

สรุปบรรทัดเดียว

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

แหล่งที่มา

  1. โจทย์ Hermes บน programming.in.th ข้อ 2037 programming.in.th/tasks/2037 (สืบค้น 11 กันยายน 2026)
  2. ต้นฉบับคือโจทย์ Hermes ของ International Olympiad in Informatics ครั้งที่ 16 (IOI 2004, ประเทศกรีซ) ตามที่ระบุไว้ในไฟล์โจทย์ของ programming.in.th