programming.in.th · ข้อ 2024

ตั๊กแตนในทุ่งดอกไม้: เมื่อข้อห้ามกว้างแค่สามช่อง เก็บสี่อันดับแรกก็พอ

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

★★★★☆ dpad hoc อ่าน 13 นาที 9 กันยายน 2026

โจทย์ · ตั๊กแตนที่กระโดดได้แค่ท่าเดียว และต้องเจอดอกใหญ่ขึ้นเรื่อย ๆ

ทุ่งดอกไม้เป็นตาราง N คูณ N แต่ละดอกมีจำนวนกลีบกำกับไว้ ตั๊กแตนเริ่มที่ดอกในแถว R คอลัมน์ C และอยากแวะดอกไม้ให้ได้มากที่สุด ภายใต้กติกาสองข้อ

  1. กระโดดได้เมื่อแกนหนึ่งขยับหนึ่งช่องพอดี และอีกแกนขยับมากกว่าหนึ่งช่อง คือจาก (r1, c1) ไป (r2, c2) ได้ก็ต่อเมื่อ |r1 - r2| = 1 และ |c1 - c2| > 1 หรือกลับกัน
  2. ดอกถัดไปต้องมีกลีบมากกว่าดอกก่อนหน้าอย่างเคร่งครัด

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

EXAMPLE
InputOutput
4
1 1
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
4
5
3 3
20 16 25 17 12
11 13 13 30 17
15 29 10 26 11
27 19 14 24 22
23 21 28 18 13
21
S
กฎการกระโดด มองจากดอกตรงกลาง ช่องเขียวคือ 16 ช่องที่ไปได้ในตารางเจ็ดคูณเจ็ดนี้ สังเกตว่ากรอบสามคูณสามรอบตัวเองไปไม่ได้เลย และช่องที่อยู่ในแถวเดียวกันหรือคอลัมน์เดียวกันก็ไปไม่ได้ ที่ไปได้คือแถวติดกันแต่คอลัมน์ห่าง หรือคอลัมน์ติดกันแต่แถวห่าง

ใบ้

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

ปัญหาที่แท้จริงอยู่ที่ราคา ดอกหนึ่งดอกกระโดดต่อได้ราว 4N ที่ ทั้งทุ่งมี N ยกกำลังสองดอก คูณกันแล้วที่ N เท่ากับ 1,500 จะเป็นหมื่นล้านครั้ง แล้วจะตัดตัวเลือกทิ้งยังไงดี

ลองเอง · พาตั๊กแตนกระโดดให้ได้ไกลที่สุด

ทุ่งนี้กลีบเพิ่มขึ้นเป็นระเบียบ จึงเหมาะกับการทำความคุ้นเคยกับกฎการกระโดดก่อน

ตั๊กแตนยืนอยู่ที่ช่องสีทอง กดดอกถัดไปที่อยากกระโดดไป กลีบต้องมากขึ้นทุกก้าว

ตอนนี้แวะไปแล้ว 1 ดอก

จากช่องที่ยืนอยู่ ให้มองแถวบนกับแถวล่างที่คอลัมน์ห่างออกไปอย่างน้อยสองช่อง และมองคอลัมน์ซ้ายกับขวาที่แถวห่างออกไปอย่างน้อยสองช่อง

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

เฉลย ตอนที่ 1 · คิดจากดอกใหญ่ลงมาหาดอกเล็ก

สองทางที่ผมลองก่อน แล้วทิ้ง

ทางแรกคือท่าตรงไปตรงมา ไล่ดูทุกช่องบนกระดานว่ากระโดดถึงไหม เขียนสิบบรรทัดจบ และตอบตัวอย่างทั้งสองชุดถูก แต่พอเอา N เท่าที่โจทย์ให้ได้จริงมาคูณ คือ 1,500 ยกกำลังสอง คูณอีก 4N ช่องที่ต้องไล่ดู มันออกมาเป็น หมื่นล้านครั้ง ซึ่งไม่มีทางจบใน 4 วินาที ผมเลยต้องหาทางลดตัวเลือก

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

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

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

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

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

คำตอบของช่องหนึ่งคือหนึ่ง บวกกับค่าที่ดีที่สุดของช่องที่กระโดดไปถึงและกลีบมากกว่า

เฉลย ตอนที่ 2 · ผู้สมัครสี่คนต่อแถว พอสำหรับทุกกรณี

จากช่อง (r, c) ปลายทางที่กระโดดถึงมีแค่สี่กลุ่ม

  • แถว r - 1 ทุกคอลัมน์ ยกเว้น c - 1, c และ c + 1
  • แถว r + 1 โดยห้ามสามคอลัมน์เดียวกัน
  • คอลัมน์ c - 1 ทุกแถว ยกเว้น r - 1, r และ r + 1
  • คอลัมน์ c + 1 โดยห้ามสามแถวเดียวกัน

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

หัวใจของทั้งข้อ
// ห้ามได้มากสุดสามดัชนี คือ c-1, c และ c+1
// เก็บสี่อันดับแรกที่ดัชนีไม่ซ้ำกัน จึงเหลือตัวที่ใช้ได้อย่างน้อยหนึ่งตัวเสมอ
int best(int b1, int b2, int b3) const {
    for(int i = 0; i < n; i++) if(k[i] != b1 && k[i] != b2 && k[i] != b3) return v[i];
    return 0;
}
สี่อันดับแรกของแถวนี้ เก็บไว้แค่นี้พอ คอลัมน์ 1 9 คอลัมน์ 4 8 คอลัมน์ 5 6 คอลัมน์ 8 5 คอลัมน์ที่ถูกห้าม: 4, 5, 6 เหลือใช้ได้: คอลัมน์ 1 ค่า 9
แถวหนึ่งเก็บไว้แค่สี่อันดับที่คอลัมน์ไม่ซ้ำกัน เมื่อคอลัมน์ 4, 5, 6 ถูกห้าม อันดับหนึ่งกับสองถูกตัดออก แต่ยังเหลืออันดับที่สี่ซึ่งอยู่คอลัมน์ 1 ให้ใช้ได้ ถ้าเก็บแค่สามอันดับ จะมีกรณีที่ทั้งสามถูกห้ามพร้อมกันจนไม่เหลืออะไรเลย

ระวัง · ดอกที่กลีบเท่ากันต้องมองไม่เห็นกัน

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

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

เดินตารางให้ดูหนึ่งทุ่ง

คิดทีละดอก จากกลีบมากไปกลีบน้อย บนทุ่ง 4 x 4

ลำดับการคิดของทุ่งสาธิต
ลำดับดอกที่กลีบ กระโดดต่อไปที่แวะได้ทั้งหมด
1 (4, 4) 16 ไม่มี 1
2 (1, 3) 15 (4, 4) 2
3 (3, 4) 14 (1, 3) 3
4 (2, 1) 13 (3, 4) 4
5 (1, 4) 12 (2, 1) 5
6 (4, 2) 11 (2, 1) 5
7 (2, 3) 10 (4, 2) 6
8 (4, 1) 9 (3, 4) 4
9 (1, 2) 8 (4, 1) 5
10 (3, 1) 7 (2, 3) 7
11 (3, 3) 6 (1, 2) 6
12 (4, 3) 5 (3, 1) 8
13 (2, 2) 4 (4, 3) 9
14 (3, 2) 3 (1, 3) 3
15 (1, 1) 2 (2, 3) 7
16 (2, 4) 1 (4, 3) 9

ทุ่งสาธิตนี้คือทุ่งที่สองในเกมข้างบน ตั๊กแตนเริ่มที่ช่อง (1, 1) ซึ่งมี 2 กลีบ และแวะได้มากที่สุด 7 ดอก ตัวเลขทุกตัวในตารางนี้คำนวณตอนสร้างหน้า ด้วยท่าที่บทความสอน และถูกเทียบกับท่าตรงไปตรงมาแล้วว่าตรงกัน

โค้ด

ทั้งโปรแกรมมีลูปใหญ่รอบเดียว วนตามลำดับกลีบที่เรียงไว้แล้ว แต่ละดอกถามตารางสี่ครั้ง ครั้งละไม่เกินสี่ก้าว รวมเป็นราวเก้าล้านครั้งที่ N เท่ากับ 1,500

ดูโค้ดเต็ม
grasshopper.cpp
#include <bits/stdc++.h>
using namespace std;
int N;
vector<int> val, dp;

// สี่อันดับแรกของแต่ละแถวและแต่ละคอลัมน์ โดยดัชนีห้ามซ้ำกัน
struct Top4 {
    int v[4], k[4]; int n = 0;
    void add(int nv, int nk){
        for(int i = 0; i < n; i++) if(k[i] == nk){ if(nv > v[i]){ v[i] = nv; sortit(); } return; }
        if(n < 4){ v[n] = nv; k[n] = nk; n++; sortit(); return; }
        if(nv > v[3]){ v[3] = nv; k[3] = nk; sortit(); }
    }
    void sortit(){
        for(int i = 1; i < n; i++){ int a = v[i], b = k[i], j = i - 1;
            while(j >= 0 && v[j] < a){ v[j+1] = v[j]; k[j+1] = k[j]; j--; }
            v[j+1] = a; k[j+1] = b; }
    }
    // ห้ามได้มากสุดสามดัชนี สี่อันดับจึงเหลือรอดอย่างน้อยหนึ่งเสมอ
    int best(int b1, int b2, int b3) const {
        for(int i = 0; i < n; i++) if(k[i] != b1 && k[i] != b2 && k[i] != b3) return v[i];
        return 0;
    }
};

int main(){
    int R, C;
    if(scanf("%d %d %d", &N, &R, &C) != 3) return 0;
    R--; C--;
    val.resize((size_t)N * N); dp.assign((size_t)N * N, 0);
    for(auto &x : val) scanf("%d", &x);
    vector<int> ord((size_t)N * N);
    for(size_t i = 0; i < ord.size(); i++) ord[i] = (int)i;
    sort(ord.begin(), ord.end(), [](int a, int b){ return val[a] > val[b]; });

    vector<Top4> row(N), col(N);
    size_t i = 0;
    while(i < ord.size()){
        size_t j = i;
        while(j < ord.size() && val[ord[j]] == val[ord[i]]) j++;   // กลุ่มที่กลีบเท่ากัน
        for(size_t t = i; t < j; t++){
            int id = ord[t], r = id / N, c = id % N, best = 0;
            if(r > 0)     best = max(best, row[r-1].best(c-1, c, c+1));
            if(r + 1 < N) best = max(best, row[r+1].best(c-1, c, c+1));
            if(c > 0)     best = max(best, col[c-1].best(r-1, r, r+1));
            if(c + 1 < N) best = max(best, col[c+1].best(r-1, r, r+1));
            dp[id] = best + 1;
        }
        // ใส่เข้าตารางหลังคิดทั้งกลุ่มจบ ไม่งั้นดอกกลีบเท่ากันจะมองเห็นกันเอง
        for(size_t t = i; t < j; t++){ int id = ord[t], r = id / N, c = id % N;
            row[r].add(dp[id], c); col[c].add(dp[id], r); }
        i = j;
    }
    printf("%d\n", dp[(size_t)R * N + C]);
}
ดูตัวตรวจที่ใช้เทียบ
brute_grasshopper.cpp
// ตัวตรวจ ไล่ดูทุกช่องบนกระดานว่ากระโดดถึงไหม ไม่มีโครงสร้างเร่งความเร็วใด ๆ
#include <bits/stdc++.h>
using namespace std;
int N, R, C; vector<vector<int>> a, memo;
int f(int r, int c){
    int &m = memo[r][c]; if(m) return m; int best = 1;
    for(int nr = 0; nr < N; nr++) for(int nc = 0; nc < N; nc++){
        if(a[nr][nc] <= a[r][c]) continue;
        int dr = abs(nr - r), dc = abs(nc - c);
        if(!((dr == 1 && dc > 1) || (dc == 1 && dr > 1))) continue;
        best = max(best, 1 + f(nr, nc));
    }
    return m = best;
}
int main(){
    scanf("%d %d %d", &N, &R, &C);
    a.assign(N, vector<int>(N)); memo.assign(N, vector<int>(N, 0));
    for(auto &row : a) for(auto &x : row) scanf("%d", &x);
    printf("%d\n", f(R - 1, C - 1));
}

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

ดูรุ่นที่ผิด กับเคสเล็กที่สุดที่ทำให้มันพัง
ผิดตรงจังหวะที่ใส่ค่าเข้าตาราง
// รุ่นที่ผิด ใส่ค่าเข้าตารางทันทีทีละดอก ไม่รอให้กลุ่มที่กลีบเท่ากันคิดจบก่อน
for(size_t t = i; t < j; t++){
    int id = ord[t], r = id / N, c = id % N, best = 0;
    if(r > 0)     best = max(best, row[r-1].best(c-1, c, c+1));
    if(r + 1 < N) best = max(best, row[r+1].best(c-1, c, c+1));
    if(c > 0)     best = max(best, col[c-1].best(r-1, r, r+1));
    if(c + 1 < N) best = max(best, col[c+1].best(r-1, r, r+1));
    dp[id] = best + 1;
    row[r].add(dp[id], c); col[c].add(dp[id], r);   // บรรทัดที่พัง ใส่เร็วไปหนึ่งจังหวะ
}

รุ่นนี้ตอบตัวอย่างในโจทย์ถูกทั้งสองชุด ผมกวาดทุ่งเล็กแบบไล่ค่าให้ครบเพื่อหาเคสที่เล็กที่สุด และได้ทุ่ง 3 คูณ 3 ที่ทุกดอกมีหนึ่งกลีบเท่ากันหมด เริ่มที่ช่อง (2, 1) คำตอบจริงคือ 1 เพราะไม่มีดอกไหนกลีบมากกว่าเลยสักดอก แต่รุ่นนี้ตอบ 2 เพราะดอกที่คิดทีหลังเห็นค่าของดอกกลีบเท่ากันที่ใส่ลงตารางไปก่อนแล้ว

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

เวลาที่วัดได้บนเครื่องผม ทุ่งขนาด 1,500 คูณ 1,500 ที่กลีบสุ่มเต็มช่วง ใช้เวลา 0.86 วินาที จากลิมิต 4 วินาที

ทำไมต้องสี่ ไม่ใช่สาม

คำถามที่ควรถามคือ เก็บสามอันดับพอไหม เพราะเราห้ามแค่สามคอลัมน์ คำตอบคือไม่พอ เพราะถ้าสามอันดับที่เก็บไว้ดันอยู่ที่คอลัมน์ c - 1, c และ c + 1 พอดี ก็จะไม่เหลืออะไรเลย ทั้งที่คอลัมน์อื่นในแถวนั้นยังมีค่าอยู่

อันนี้ไม่ใช่การเดา ผมคอมไพล์รุ่นที่เก็บสามอันดับแล้วสุ่มเทียบกับตัวตรวจ มันพังที่ทุ่งขนาด 5 คูณ 5 ซึ่งคำตอบจริงคือ 8 แต่รุ่นสามอันดับตอบ 7 ส่วนรุ่นสี่อันดับผ่านทุกเคสในชุดเดียวกัน

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

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

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

ทบทวนพื้นฐาน · ทางยาวที่สุดบนกราฟไร้วงจร

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

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

ถ้าอยากเห็นการยุบกราฟให้ไม่มีวงจรก่อนจะคิดแบบนี้ ลองอ่าน กราฟที่ทุกปมมีทางออกทางเดียว

บิลค่าใช้จ่าย · สามทางที่คิด นับด้วยหน่วยเดียวกัน
ทางขอบเขตที่มันไหวงานคร่าว ๆ
ไล่ดูทุกช่องที่กระโดดถึงN ไม่เกินราวร้อย13.5 หมื่นล้านครั้งที่ N = 1500
ต้นไม้ช่วงประจำแถวและคอลัมน์N ถึงหลักพันราว 100 ล้านครั้ง บวกโครงสร้าง 3,000 ต้น
เก็บสี่อันดับแรกของแถวและคอลัมน์N ถึง 15009 ล้านครั้ง
ทั้งสามแถวนับจำนวนครั้งที่โปรแกรมต้องหยิบตัวเลขขึ้นมาดู แถวกลางไหวเรื่องเวลาแต่ตายเรื่องหน่วยความจำ เพราะต้องมีต้นไม้ช่วงสามพันต้นในโควตา 35 เมกะไบต์ ส่วนแถวล่างใช้ที่แค่แถวละสี่ตัว คือหมื่นสองพันตัวสำหรับทั้งทุ่ง

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

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

แหล่งที่มา

  1. โจทย์ Grasshopper บน programming.in.th ข้อ 2024 programming.in.th/tasks/2024 (สืบค้น 9 กันยายน 2026)
  2. ต้นทางของโจทย์คือ COCI 2008/2009 Contest #1 วันที่ 18 ตุลาคม 2008