programming.in.th · ข้อ 2025

อุโมงค์ตัวตุ่น: พอรู้ว่าจะปิดเส้นไหน ที่เหลือไม่มีอะไรให้เลือกอีกแล้ว

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

★★★★★ treedpbfs อ่าน 14 นาที 9 กันยายน 2026

โจทย์ · ตัวตุ่นที่ยอมรื้ออุโมงค์ได้แค่เส้นเดียว

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

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

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

EXAMPLE
InputOutput
4
1 2
2 3
3 4
2
1 2
1 3
7
1 3
2 3
2 7
4 3
7 5
3 6
3
2 3
7 3

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

ใบ้

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

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

ลองเอง · เลือกเส้นที่จะปิด แล้วเลือกจุดที่จะต่อ

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

1 2 3 4 5 6

กดที่เส้นเพื่อเลือกอุโมงค์ที่จะปิดก่อน

ยังไม่ได้เลือกอะไร

ปิดหนึ่งเส้นแล้วผังจะแตกเป็นสองก้อน จากนั้นกดหนึ่งห้องในแต่ละก้อนเพื่อเปิดอุโมงค์ใหม่

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

เฉลย ตอนที่ 1 · ถ้ารู้ว่าปิดเส้นไหน ที่เหลือไม่มีอะไรให้เลือกแล้ว

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

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

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

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

ปิดเส้นหนึ่งแล้วได้สองก้อน ให้ก้อนแรกมีระยะไกลสุดในตัวเองเท่ากับ d1 และก้อนที่สองเท่ากับ d2 ค่าพวกนี้ไม่เปลี่ยนไม่ว่าจะต่ออุโมงค์ใหม่ตรงไหน เพราะอุโมงค์ใหม่ไม่ได้ทำให้ทางภายในก้อนเดียวกันสั้นลง

ส่วนทางที่วิ่งข้ามอุโมงค์ใหม่ ยาวเท่ากับระยะไกลสุดจากปลายฝั่งนี้ บวกหนึ่ง บวกระยะไกลสุดจากปลายฝั่งโน้น ค่านี้น้อยที่สุดเมื่อเลือกห้องที่มีระยะไกลสุดน้อยที่สุดในก้อนของมัน ซึ่งเรียกว่าจุดกึ่งกลาง (center) และค่าที่ได้เรียกว่ารัศมี (radius) ซึ่งเท่ากับเส้นผ่านศูนย์กลางหารสองปัดขึ้นเสมอ

หัวใจของทั้งข้อ
// ปิดเส้นหนึ่งแล้วได้สองก้อน ยาว d1 กับ d2
// ต่อที่จุดกึ่งกลางของทั้งสองก้อน ทางที่วิ่งข้ามอุโมงค์ใหม่จึงยาว รัศมี + รัศมี + 1
// คำตอบของเส้นนี้คือตัวที่ใหญ่ที่สุดในสามตัว
int s = max(max(d1, d2), (d1 + 1) / 2 + (d2 + 1) / 2 + 1);
1 2 3 4 5 6 7 จุดกึ่งกลาง ไกลสุดแค่ 3 ห้อง ทั้งสองทาง
ทางเดิน 7 ห้องที่เส้นผ่านศูนย์กลางเท่ากับ 6 ห้องกลางคือห้องที่ 4 ซึ่งไกลจากปลายทั้งสองข้างแค่ 3 ห้อง นี่คือค่าที่น้อยที่สุดที่ห้องใดห้องหนึ่งจะมีได้ในก้อนนี้ จึงเป็นห้องที่ควรต่ออุโมงค์ใหม่

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

เดินตารางให้ดูหนึ่งผัง

ลองปิดทีละเส้นบนผัง 9 ห้อง ซึ่งตอนแรกไกลสุด 5

ค่าของทุกเส้นที่ปิดได้
ปิดอุโมงค์ ก้อนซ้ายยาว ก้อนขวายาว รัศมีรวมบวกหนึ่ง ได้ค่า
1 ถึง 2 0 5 4 5
2 ถึง 3 3 4 5 5
3 ถึง 4 5 1 5 5
4 ถึง 5 5 0 4 5
3 ถึง 6 5 1 5 5
6 ถึง 7 5 0 4 5
2 ถึง 8 4 1 4 4
8 ถึง 9 4 0 3 4

ผังนี้ตอนแรกไกลสุด 5 ห้อง หลังรื้อเหลือ 4 ห้อง โดยปิดอุโมงค์ 2 ถึง 8 แล้วเปิดเส้นใหม่ระหว่างห้อง 3 กับห้อง 8 ตัวเลขทุกช่องมาจากการเดินจริงบนผังนี้ตอนสร้างหน้า

เฉลย ตอนที่ 2 · หาความยาวของสองก้อนให้ครบทุกเส้น ในการไล่รอบเดียว

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

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

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

โน้ต · ทำไมต้องสามอันดับ ไม่ใช่สองอันดับ

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

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

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

โค้ด

ดูโค้ดเต็ม
mole.cpp
#include <bits/stdc++.h>
using namespace std;
int N;
vector<int> head_, nxt_, to_;
void addEdge(int u, int v){ to_.push_back(v); nxt_.push_back(head_[u]); head_[u] = (int)to_.size() - 1; }
vector<int> par, ord_, h, dd, uh, ud;

// เดินจาก s ในกราฟที่ตัดเส้น (ba, bb) ออกแล้ว คืนระยะทางกับพ่อของทุกปมที่ไปถึง
void bfs(int s, int ba, int bb, vector<int>&d, vector<int>&pa, vector<int>&seen){
    d[s] = 0; pa[s] = 0; seen.clear(); seen.push_back(s);
    for(size_t i = 0; i < seen.size(); i++){ int u = seen[i];
        for(int e = head_[u]; e != -1; e = nxt_[e]){ int v = to_[e];
            if((u == ba && v == bb) || (u == bb && v == ba)) continue;
            if(d[v] < 0){ d[v] = d[u] + 1; pa[v] = u; seen.push_back(v); } } }
}

int main(){
    if(scanf("%d", &N) != 1) return 0;
    head_.assign(N + 1, -1);
    for(int i = 0; i < N - 1; i++){ int a, b; scanf("%d %d", &a, &b); addEdge(a, b); addEdge(b, a); }
    if(N == 1){ printf("0\n"); return 0; }              // ไม่มีอุโมงค์ให้ปิด

    par.assign(N + 1, 0); ord_.reserve(N);
    h.assign(N + 1, 0); dd.assign(N + 1, 0); uh.assign(N + 1, 0); ud.assign(N + 1, 0);
    // เรียงแบบกว้างก่อนแล้วไล่ย้อน แทนการเรียกซ้ำ ต้นไม้สูงสามแสนชั้นจึงไม่ทำสแต็กแตก
    vector<char> vis(N + 1, 0); ord_.push_back(1); vis[1] = 1;
    for(size_t i = 0; i < ord_.size(); i++){ int u = ord_[i];
        for(int e = head_[u]; e != -1; e = nxt_[e]){ int v = to_[e];
            if(!vis[v]){ vis[v] = 1; par[v] = u; ord_.push_back(v); } } }

    // ขาลง h = ความสูงของกิ่ง, dd = เส้นผ่านศูนย์กลางภายในกิ่ง
    for(int i = N - 1; i >= 0; i--){ int v = ord_[i]; int a1 = 0, a2 = 0;
        for(int e = head_[v]; e != -1; e = nxt_[e]){ int c = to_[e]; if(c == par[v]) continue;
            int a = h[c] + 1; if(a > a1){ a2 = a1; a1 = a; } else if(a > a2) a2 = a;
            dd[v] = max(dd[v], dd[c]); }
        h[v] = a1; dd[v] = max(dd[v], a1 + a2); }

    // ขาขึ้น uh = ระยะไกลสุดที่ออกไปทางพ่อ, ud = เส้นผ่านศูนย์กลางของส่วนที่เหลือ
    for(int i = 0; i < N; i++){ int p = ord_[i];
        int a1 = 0, a2 = 0, a3 = 0, i1 = -1, i2 = -1;     // สามอันดับของระยะลงไปในลูก
        int d1 = 0, d2 = 0, j1 = -1;                       // สองอันดับของเส้นผ่านศูนย์กลางในลูก
        for(int e = head_[p]; e != -1; e = nxt_[e]){ int c = to_[e]; if(c == par[p]) continue;
            int a = h[c] + 1;
            if(a > a1){ a3 = a2; a2 = a1; i2 = i1; a1 = a; i1 = c; }
            else if(a > a2){ a3 = a2; a2 = a; i2 = c; }
            else if(a > a3){ a3 = a; }
            int d = dd[c];
            if(d > d1){ d2 = d1; d1 = d; j1 = c; } else if(d > d2) d2 = d; }
        for(int e = head_[p]; e != -1; e = nxt_[e]){ int c = to_[e]; if(c == par[p]) continue;
            int b1 = (i1 == c) ? a2 : a1;                  // ระยะลงที่ยาวสุด ไม่นับกิ่งของ c
            int b2 = (i1 == c) ? a3 : ((i2 == c) ? a3 : a2);
            int bd = (j1 == c) ? d2 : d1;                  // เส้นผ่านศูนย์กลางของลูกตัวอื่น
            int s1 = uh[p], s2 = 0;                        // สองทางที่ยาวสุดจาก p รวมทางขึ้นข้างบน
            if(b1 > s1){ s2 = s1; s1 = b1; } else s2 = b1;
            if(b2 > s2) s2 = b2;
            ud[c] = max(max(ud[p], bd), s1 + s2);
            uh[c] = 1 + max(uh[p], b1); } }

    int best = INT_MAX, bv = -1;
    for(int v = 2; v <= N; v++){
        int d1 = dd[v], d2 = ud[v];
        int s = max(max(d1, d2), (d1 + 1) / 2 + (d2 + 1) / 2 + 1);
        if(s < best){ best = s; bv = v; } }

    // รู้แล้วว่าปิดเส้นไหน จึงค่อยเดินกว้างสองรอบต่อข้าง เพื่อหาจุดกึ่งกลางจริง ๆ
    int pu = par[bv];
    vector<int> d(N + 1, -1), pa(N + 1, 0), seen;
    auto centerOf = [&](int s) -> int {
        fill(d.begin(), d.end(), -1); bfs(s, pu, bv, d, pa, seen);
        int u = s; for(int x : seen) if(d[x] > d[u]) u = x;
        fill(d.begin(), d.end(), -1); bfs(u, pu, bv, d, pa, seen);
        int w = u; for(int x : seen) if(d[x] > d[w]) w = x;
        int steps = d[w] / 2, cur = w; while(steps--) cur = pa[cur];
        return cur; };
    printf("%d\n%d %d\n%d %d\n", best, pu, bv, centerOf(bv), centerOf(pu));
}
ดูตัวตรวจที่ใช้เทียบ
brute_mole.cpp
// ตัวตรวจ ปิดทุกเส้น ต่อทุกคู่ห้อง แล้ววัดระยะไกลสุดจริงทุกครั้ง
// ไม่มีคำว่ารัศมีหรือจุดกึ่งกลางอยู่ในนี้เลย
#include <bits/stdc++.h>
using namespace std;
int N; vector<pair<int,int>> E;
int diamOf(vector<vector<int>>&g, vector<int>&comp){
    int best = 0;
    for(int s : comp){ vector<int> d(N + 1, -1); queue<int> q; q.push(s); d[s] = 0;
        while(!q.empty()){ int u = q.front(); q.pop();
            for(int v : g[u]) if(d[v] < 0){ d[v] = d[u] + 1; q.push(v); } }
        for(int v : comp) best = max(best, d[v]); }
    return best; }
int main(){
    scanf("%d", &N); E.resize(N - 1);
    for(auto &e : E) scanf("%d %d", &e.first, &e.second);
    int ans = INT_MAX;
    for(int rm = 0; rm < N - 1; rm++){
        vector<vector<int>> g(N + 1);
        for(int i = 0; i < N - 1; i++) if(i != rm){
            g[E[i].first].push_back(E[i].second); g[E[i].second].push_back(E[i].first); }
        vector<int> col(N + 1, 0), A, B;
        { queue<int> q; q.push(E[rm].first); col[E[rm].first] = 1;
          while(!q.empty()){ int u = q.front(); q.pop(); A.push_back(u);
            for(int v : g[u]) if(!col[v]){ col[v] = 1; q.push(v); } } }
        for(int v = 1; v <= N; v++) if(!col[v]) B.push_back(v);
        for(int x : A) for(int y : B){
            g[x].push_back(y); g[y].push_back(x);
            vector<int> all; for(int v = 1; v <= N; v++) all.push_back(v);
            ans = min(ans, diamOf(g, all));
            g[x].pop_back(); g[y].pop_back(); } }
    printf("%d\n", ans);
}

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

ผมเทียบสองโปรแกรมบนต้นไม้ 5 ทรง คือทางเดินยาว ดาว หนอนผีเสื้อ ต้นไม้ทวิภาค และต้นไม้สุ่ม ขนาด 2 ถึง 14 ห้อง รวม 990 เคส และทุกเคสตรวจแผนที่พิมพ์ออกมาด้วย คือปิดเส้นที่บอก ต่อเส้นที่บอก แล้ววัดว่าได้ค่าตามที่พิมพ์ไว้จริงไหม ตรงกันทั้งหมด หน้าเว็บนี้ก็รันด่านตรวจแบบเดียวกันตอนบิลด์ 120 ชุด

เวลาที่วัดได้บนเครื่องผม ที่ N เท่ากับ 300,000 ทั้งทางเดินยาวสามแสนห้อง ต้นไม้สุ่ม และดาวดวงเดียว ใช้เวลา 0.19 วินาที จากลิมิต 3 วินาที

ระวัง · ความลึกของต้นไม้

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

เรื่องนี้มีเขียนไว้ละเอียดใน DP บนต้นไม้ ซึ่งเป็นบทปูพื้นของท่านี้

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

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

ทบทวนพื้นฐาน · หาเส้นผ่านศูนย์กลางด้วยการเดินกว้างสองรอบ

วิธีที่สั้นที่สุดคือเดินกว้างจากห้องไหนก็ได้ ไปเจอห้องที่ไกลที่สุดเรียกว่า u แล้วเดินกว้างจาก u อีกรอบ ห้องที่ไกลที่สุดรอบนี้คืออีกปลายของเส้นผ่านศูนย์กลางพอดี ระหว่างทางเก็บพ่อไว้ด้วย แล้วเดินย้อนกลับมาครึ่งทาง ก็จะได้จุดกึ่งกลาง

ท่านี้ใช้ได้เฉพาะกับต้นไม้ ไม่ใช่กราฟทั่วไป เพราะมันอาศัยข้อเท็จจริงว่า ทางระหว่างสองห้องมีทางเดียว ถ้าโจทย์ไหนมีวงจร ท่านี้จะให้คำตอบผิดเงียบ ๆ

บิลค่าใช้จ่าย
ทางขอบเขตที่มันไหวงานคร่าว ๆ
ปิดทุกเส้น แล้วเดินวัดสองก้อนใหม่ทุกครั้งN ไม่เกินราวสามพัน300,000 คูณ 300,000 คือเก้าหมื่นล้าน
ไล่ค่าขึ้นลงรอบเดียว แล้วเดินกว้างเฉพาะเส้นที่ชนะN ถึง 300,000ราว 900,000 ครั้ง
สองแถวนี้นับจำนวนครั้งที่ต้องแตะปมหนึ่งปมเหมือนกัน ต่างกันแค่แถวบนแตะทุกปมใหม่ทุกครั้งที่ปิดเส้น ส่วนแถวล่างแตะทุกปมสามรอบพอดี คือขาลง ขาขึ้น และการเดินกว้างของเส้นที่ชนะ

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

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

แหล่งที่มา

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