programming.in.th · ข้อ 2038

ดูแลทางเดิน: ทางที่ถูกทิ้งไปแล้ว ไม่มีวันกลับมา

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

★★★☆☆ mstdsugreedy อ่าน 10 นาที 11 กันยายน 2026

โจทย์ · ดูแลทางเดินให้วัวไปได้ทุกจุด

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

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

ชื่อโจทย์ maintain อ่านว่า "เมนเทน" แปลว่าดูแลรักษาให้คงสภาพ มาจากภาษาละตินที่แปลตรง ๆ ว่า "ถือไว้ในมือ" (manu tenere) ซึ่งเข้ากับข้อนี้พอดี เพราะทั้งข้อคือคำถามว่าต้องถืออะไรไว้ในมือข้ามสัปดาห์บ้าง

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

EXAMPLE
InputOutput
4 6
1 2 10
1 3 8
3 2 3
1 4 3
1 3 6
2 1 2
-1
-1
-1
14
12
8

อ่านตัวอย่างนี้ยังไง

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

3 สัปดาห์แรกตอบ -1 เพราะยังไม่มีทางไหนแตะจุด 4 เลย ทางแรกที่แตะจุด 4 มาในสัปดาห์ที่ 4 สัปดาห์นั้นมีทางให้เลือก 4 เส้น ชุดที่สั้นที่สุดคือ 1-4 (3), 3-2 (3), 1-3 (8) ยาวรวม 14 อีกสองสัปดาห์ที่เหลือลองคิดเองก่อนนะครับ เกมข้างล่างใช้ชุดนี้เป็นด่านแรก

ใบ้

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

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

ลองเอง · เลือกทางให้สั้นที่สุดทีละสัปดาห์

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

10 8 3 3 6 2 1 2 3 4

 

กดที่ทางเพื่อเลือกดูแลหรือเลิกดูแล ทางสีทองคือทางที่เพิ่งเจอในสัปดาห์นี้

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

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

เฉลย ตอนที่ 1 · ทำใหม่หมดทุกสัปดาห์ ถูกแต่เฉียดเวลา

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

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

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

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

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

เฉลย ตอนที่ 2 · ทางที่ถูกทิ้งไปแล้ว ไม่มีวันกลับมา

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

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

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

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

10 8 3 3 6 2 1 2 3 4 วงที่เพิ่งเกิดในสัปดาห์นี้ ทางใหม่ 2-1 (2) ยาวสุดในวง ทิ้งถาวร ต้นไม้หลังจบสัปดาห์ ถูกทิ้งไปตั้งแต่สัปดาห์ก่อน
สัปดาห์ที่ 6 ของตัวอย่าง ทางใหม่ 2-1 (2) ปิดวงกับต้นไม้เดิม ทางที่ยาวที่สุดในวงคือ 1-3 (6) จึงถูกทิ้ง ส่วนทาง 1-2 (10) (ทิ้งสัปดาห์ที่ 3) กับ 1-3 (8) (ทิ้งสัปดาห์ที่ 5) ถูกทิ้งไปก่อนแล้ว และไม่ถูกหยิบมาคิดอีกเลย

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

แกะคำศัพท์ · cycle

cycle อ่านว่า "ไซเคิล" แปลว่าวง คือเดินตามทางแล้ววนกลับมาจุดเดิมได้โดยไม่ใช้ทางซ้ำ รากเดียวกับ bicycle ที่แปลว่าสองวงล้อ มาจากคำกรีกที่แปลว่าวงกลม

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

ของที่ถือข้ามสัปดาห์
// ของที่ต้องจำข้ามสัปดาห์มีแค่ต้นไม้ล่าสุด ไม่เกิน N - 1 เส้น
keep.insert(upper_bound(keep.begin(), keep.end(), e), e);   // + ทางใหม่ 1 เส้น
// Kruskal บนรายการนี้ ทางที่ปิดวงถูกทิ้ง และไม่ต้องเก็บไว้อีกเลย

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

ทำไมทางที่ถูกทิ้ง ไม่ต้องเรียกกลับเลย

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

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

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

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

ข้อมูลมีแต่เพิ่ม ทางที่ถูกตัดสินว่ายาวเกินไปแล้วจึงยาวเกินไปตลอดกาล ของที่ต้องถือไว้ในมือมีแค่ต้นไม้ 3 เส้นของตัวอย่าง หรือ N - 1 เส้นของข้อจริง

เดินตัวอย่างทีละสัปดาห์

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

ตัวอย่างในโจทย์ ทีละสัปดาห์
สัปดาห์ทางใหม่ต้นไม้ที่ถือไว้ทิ้งคำตอบ
1 1-2 (10) 1-2 (10) · -1
2 1-3 (8) 1-3 (8), 1-2 (10) · -1
3 3-2 (3) 3-2 (3), 1-3 (8) 1-2 (10) -1
4 1-4 (3) 1-4 (3), 3-2 (3), 1-3 (8) · 14
5 1-3 (6) 1-4 (3), 3-2 (3), 1-3 (6) 1-3 (8) 12
6 2-1 (2) 2-1 (2), 1-4 (3), 3-2 (3) 1-3 (6) 8

คอลัมน์ต้นไม้ไม่เคยยาวเกิน 3 เส้น ส่วนคอลัมน์ทิ้งคือทุกอย่างที่ท่าตรง ๆ ต้องหยิบมาดูซ้ำทุกสัปดาห์ คำตอบทั้งหกบรรทัดคือ -1, -1, -1, 14, 12, 8 ตรงกับเอาต์พุตของโจทย์

โค้ด

ดูโค้ดเต็ม
maintain.cpp
#include <bits/stdc++.h>
using namespace std;
int par[205];
int findp(int x){ return par[x] == x ? x : par[x] = findp(par[x]); }
int main(){
    int n, w;
    if(scanf("%d %d", &n, &w) != 2) return 0;
    // เก็บแค่เส้นของต้นไม้ปัจจุบัน เรียงจากสั้นไปยาว เป็น (ความยาว, ปลาย, ปลาย)
    vector<array<int,3>> keep;
    for(int week = 0; week < w; week++){
        int u, v, z;
        scanf("%d %d %d", &u, &v, &z);
        // แทรกเส้นใหม่ลงตำแหน่งของมัน รายการจึงเรียงอยู่เสมอ ไม่ต้อง sort ใหม่
        array<int,3> e = {z, u, v};
        keep.insert(upper_bound(keep.begin(), keep.end(), e), e);
        // Kruskal บนเส้นไม่เกิน N เส้น
        for(int i = 1; i <= n; i++) par[i] = i;
        vector<array<int,3>> tree;
        long long total = 0;
        for(auto &t : keep){
            int a = findp(t[1]), b = findp(t[2]);
            if(a == b) continue;   // เส้นนี้ปิดวง และยาวที่สุดในวงนั้น ทิ้งถาวร
            par[a] = b;
            tree.push_back(t);
            total += t[0];
        }
        keep = tree;
        if((int)tree.size() == n - 1) printf("%lld\n", total);
        else printf("-1\n");
    }
}

ใช้ upper_bound หาที่แทรก รายการจึงเรียงอยู่ตลอด ไม่ต้องเรียงใหม่ทั้งก้อน ส่วน DSU รีเซ็ตใหม่ทุกสัปดาห์ เพราะต้นไม้ของสัปดาห์นี้อาจไม่ใช่ต้นไม้เดิมบวกหนึ่งเส้น ผลรวมอยู่ไม่เกิน 199 คูณ 10,000 ใส่ int ได้ แต่ผมใช้ long long ไว้ก่อน ไม่เสียอะไร

ดูท่าทำใหม่หมดทุกสัปดาห์ (ตัวเทียบ)
maintain_naive.cpp
#include <bits/stdc++.h>
using namespace std;
int par[205];
int findp(int x){ return par[x] == x ? x : par[x] = findp(par[x]); }
int main(){
    int n, w;
    if(scanf("%d %d", &n, &w) != 2) return 0;
    vector<array<int,3>> all;   // ทุกเส้นที่เคยเจอ ไม่ทิ้งอะไรเลย
    for(int week = 0; week < w; week++){
        int u, v, z;
        scanf("%d %d %d", &u, &v, &z);
        all.push_back({z, u, v});
        // Kruskal ใหม่หมดทุกสัปดาห์ เรียงสำเนาของทุกเส้นที่เคยเจอ
        vector<array<int,3>> es = all;
        sort(es.begin(), es.end());
        for(int i = 1; i <= n; i++) par[i] = i;
        int used = 0;
        long long total = 0;
        for(auto &t : es){
            int a = findp(t[1]), b = findp(t[2]);
            if(a == b) continue;
            par[a] = b;
            used++;
            total += t[0];
        }
        if(used == n - 1) printf("%lld\n", total);
        else printf("-1\n");
    }
}

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

ดูตัวตรวจที่ใช้เทียบ
brute_maintain.cpp
// ตัวตรวจ ลองทุกเซตย่อยของเส้นที่รู้จัก เลือกเซตที่ทำให้ทุกจุดถึงกันแล้วยาวรวมน้อยที่สุด
// ไม่เรียงเส้น ไม่ทิ้งเส้น ไม่มี Kruskal ใช้ได้กับกราฟเล็ก ๆ เท่านั้น
#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, w;
    scanf("%d %d", &n, &w);
    vector<int> U(w), V(w), Z(w);
    for(int k = 0; k < w; k++){
        scanf("%d %d %d", &U[k], &V[k], &Z[k]);
        int m = k + 1;
        long long best = -1;
        for(int mask = 0; mask < (1 << m); mask++){
            // เริ่มจากจุด 1 แล้วแผ่ไปตามเส้นในเซตนี้ซ้ำ ๆ จนไม่มีจุดใหม่
            vector<int> seen(n + 1, 0);
            seen[1] = 1;
            bool grow = true;
            while(grow){
                grow = false;
                for(int i = 0; i < m; i++) if(mask >> i & 1)
                    if(seen[U[i]] != seen[V[i]]){ seen[U[i]] = seen[V[i]] = 1; grow = true; }
            }
            if(count(seen.begin() + 1, seen.end(), 1) != n) continue;
            long long s = 0;
            for(int i = 0; i < m; i++) if(mask >> i & 1) s += Z[i];
            if(best < 0 || s < best) best = s;
        }
        printf("%lld\n", best);
    }
}

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

ท่าที่ดูเข้าท่า แต่ผิด

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

อีกท่าที่ใกล้เคียงคือเทียบทางใหม่กับทางเดิมที่เชื่อมจุดคู่เดียวกันเท่านั้น ชุดห้าเหลี่ยมหักท่านี้ ทางใหม่ 1-3 (1) ไม่มีทางเดิมที่เชื่อมคู่เดียวกันเลย แต่ทางที่ต้องออกคือ 2-4 (6) ที่อยู่อีกฟากของวง จึงต้องมองทั้งวง การมองแค่ทางคู่ขนานไม่พอ

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

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

เวลาที่วัดได้บนเครื่องผม อินพุต 200 จุด 5,999 สัปดาห์ ท่าเต็มใช้ราว 0.02 วินาที ทั้งแบบความยาวสุ่ม แบบความยาวซ้ำกันเยอะ และแบบที่ทางใหม่สั้นลงทุกสัปดาห์จนต้นไม้ถูกเปลี่ยนตลอด ท่าทำใหม่หมดใช้ 0.75 วินาทีกับชุดแรก และ 1.10 วินาทีกับชุดที่สอง (ชุดที่สามเร็วกว่ามาก เพราะรายการที่ต้องเรียงแทบจะเรียงอยู่แล้ว) ทั้งสองท่าตอบตรงกันทุกบรรทัดในทั้งสามชุด

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

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

บิลค่าใช้จ่าย
ทางทางที่แตะรวมทุกสัปดาห์เวลาที่วัดจริง
Kruskal ใหม่บนทุกทางที่เคยเจอ18,003,000 เส้น (ยังไม่นับการเรียงทุกสัปดาห์)0.75 ถึง 1.10 วินาที
เก็บแค่ทางของต้นไม้ แทรกทางใหม่แล้ว Kruskalไม่เกิน 1,200,000 เส้น0.02 วินาที
ทั้งสองแถวนับหน่วยเดียวกัน คือจำนวนครั้งที่ Kruskal หยิบทางหนึ่งเส้นขึ้นมาถาม DSU ที่ขอบเขตเต็ม 200 จุด 6,000 สัปดาห์ แถวบนยังต้องเรียงทุกทางใหม่ทุกสัปดาห์ด้วย แถวล่างแค่แทรกทีละเส้น

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

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

แหล่งที่มา

  1. โจทย์ Maintain บน programming.in.th ข้อ 2038 programming.in.th/tasks/2038 (สืบค้น 11 กันยายน 2026)
  2. ต้นฉบับคือโจทย์ maintain วันแรกของ International Olympiad in Informatics 2003 ที่สหรัฐอเมริกา ตามที่ฉบับบน programming.in.th อ้างไว้