programming.in.th · ข้อ 2010

อัญมณีในท้องปลา: อย่าหักลบทีหลัง ให้ตั้งกฎว่าคำตอบเป็นของใครตั้งแต่แรก

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

★★★★★ countingsegment treegreedy อ่าน 13 นาที 7 กันยายน 2026

โจทย์ · จับปลาหนึ่งตัว ได้อัญมณีในท้องมันทั้งหมด

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

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

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

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

ชุดที่เป็นไปได้มี 11 แบบ คือ [1] [2] [3] [1,2] [1,3] [2,3] [3,3] [1,2,3] [1,3,3] [2,3,3] [1,2,3,3] และ 11 หารเอาเศษด้วย 7 ได้ 4 ตรงกับรายการที่โจทย์ยกมาให้ดูทั้งหมด

ใบ้

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

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

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

ลองเอง · เก็บชุดอัญมณีให้ครบทุกแบบ

ปลาสามตัวสองชนิด พอให้เห็นว่าเพดานทำงานยังไง

เลือกชนิดที่จะจับ ตัวที่ยาวที่สุดของชนิดนั้นจะเป็นตัวกำหนดเพดาน

เลือกชนิดที่จะจับก่อน แล้วค่อยปรับจำนวนอัญมณีแต่ละชนิดที่จะเก็บ

เก็บได้ 0 จาก 0 ชุดที่ต่างกัน

ยังไม่ได้เก็บชุดไหนเลย

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

เฉลย · ตัวจับผูกกับชนิด ไม่ใช่กับตัวปลา

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

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

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

ตอนตรวจ ผมสุ่ม 4,000 ชุดเทียบสามอย่างพร้อมกัน คือเฉลยเต็ม เฉลยชุดทดสอบย่อย และตัวไล่นับที่โยนทุกชุดที่หาได้ลง set ตรง ๆ โดยไม่มีทฤษฎีกันซ้ำเลยสักบรรทัด ตัวที่สามสำคัญที่สุด เพราะสองตัวแรกใช้เหตุผลเรื่องการกันนับซ้ำชุดเดียวกัน ถ้าเหตุผลนั้นผิด มันจะผิดพร้อมกันทั้งคู่

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

ขั้นที่ 1 กำหนดว่าปลาตัวหนึ่งจับมาแล้วได้อะไรบ้าง

ปลา X ยาว Lx กินปลาที่ยาวไม่เกิน Lx / 2 ได้ตรง ๆ ส่วนอัญมณีที่มาทางอ้อม ต้องผ่านปลากลางทางที่ยาวไม่เกิน Lx / 2 อยู่แล้ว และปลาที่ตัวกลางกินได้ก็ยาวไม่เกิน Lx / 4 ซึ่งก็ยังอยู่ในกลุ่มเดิม

จับปลา X แล้วได้อัญมณีของตัวมันเอง บวกกับชุดย่อยใด ๆ ของปลาที่ยาวไม่เกินครึ่งหนึ่งของมัน

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

ขั้นที่ 2 ตัวจับที่ดีที่สุดของแต่ละชนิด

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

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

ขั้นที่ 3 กันการนับซ้ำ

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

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

  • ชุดนั้นไม่มีอัญมณีของชนิดหลังเลย หรือ
  • ชุดนั้นใช้อัญมณีของชนิดปัจจุบันเต็มเพดาน ซึ่งชนิดหลังให้ไม่ได้

การนับจึงแตกเป็นสองกรณีที่ไม่ทับกัน

สองกรณีของชนิดที่อยู่ตำแหน่ง i
กรณีจำนวนอัญมณีชนิดตัวเองชนิดที่มาทีหลังจำนวนชุด
(ข) ไม่เต็มเพดาน 1 ถึง m ของตัวเอง ต้องเป็นศูนย์ทั้งหมด m ของตัวเอง × ผลคูณของชนิดก่อนหน้า
(ก) เต็มเพดาน m ของตัวเอง บวกหนึ่ง ใส่ได้จนถึงชนิดที่กลืนปลาตัว x ได้ ผลคูณของชนิดก่อนหน้า × ผลคูณช่วง (i, J)
ปลาตัว x คือปลาชนิดตัวเองที่เล็กที่สุดที่ตัวแทนของชนิดตัวเองยังกินไม่ได้ และ J คือชนิดแรกที่อยู่หลัง i ซึ่งกลืน x เข้าไปได้ ชนิดตั้งแต่ J เป็นต้นไปจึงมองเห็นอัญมณีชนิดตัวเองครบเพดาน และแย่งสิทธิ์การนับไปได้
หัวใจของทั้งข้อ
ll own = m[i];                     // จำนวนปลาชนิดนี้ที่ตัวโตสุดของชนิดนี้กินได้
ll left = prod(0, i - 1);          // ผลคูณของ (จำนวน + 1) ของชนิดที่มาก่อนหน้า

// กรณี (ข) ชนิดตัวเองไม่เต็มเพดาน  ชนิดที่มาทีหลังต้องเป็นศูนย์ทั้งหมด
ans = (ans + own % MOD * left) % MOD;

// กรณี (ก) ชนิดตัวเองเต็มเพดาน  ชนิดที่มาทีหลังยังใส่ได้จนกว่าจะถึงตัวที่กลืน x ได้
int J = first_index_after_i_whose_prefix_swallows(x);
ans = (ans + left * prod(i + 1, J - 1)) % MOD;

แกะตัวอย่างในโจทย์ทีละชนิด

กดถัดไปเพื่อไล่ชนิดทีละชนิด

เรียงชนิดตามตัวแทนจากเล็กไปใหญ่
ลำดับชนิดตัวแทนยาว กินได้กี่ตัวต่อชนิดเพดานชนิดตัวเอง xJ กรณี (ข)กรณี (ก)
1 2 2 0 0 0 1 2 2 0 1
2 1 5 1 0 1 1 4 3 0 2
3 3 8 1 1 1 2 8 4 4 4

คอลัมน์ "กินได้กี่ตัวต่อชนิด" อ่านตามลำดับที่เรียงไว้ ไม่ใช่ตามหมายเลขชนิดเดิม รวมสองคอลัมน์สุดท้ายได้ 11 ซึ่งตรงกับการไล่นับตรง ๆ ที่ได้ 11 แบบ และ 11 หารเอาเศษด้วย 7 ได้ 4

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

อีกมุมหนึ่ง: ไม่ต้องใช้โครงสร้างข้อมูลอะไรเลยก็ได้ 70 คะแนน

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

แต่ถ้า K ไม่เกิน 7,000 ก็ไม่ต้องใช้อะไรเลย คูณไล่ตรง ๆ ทุกรอบ เป็น K² ซึ่งคือ 49,000,000 ครั้ง ผ่านสบายในสามวินาที ได้ 70 คะแนน

ดูโค้ดของชุดทดสอบย่อย
fish_sub.cpp
// เฉลยชุดทดสอบย่อย K ไม่เกิน 7,000 (70 คะแนน)
// สูตรเดียวกับเฉลยเต็ม ต่างแค่ไม่ยกเซกเมนต์ทรีมาใช้ คูณไล่เอาตรง ๆ เป็น O(K^2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    int F, K; ll MOD;
    scanf("%d %d %lld", &F, &K, &MOD);
    vector<int> L(F), T(F);
    vector<vector<int>> byType(K + 1);
    for (int i = 0; i < F; i++) { scanf("%d %d", &L[i], &T[i]); byType[T[i]].push_back(L[i]); }
    for (int t = 1; t <= K; t++) sort(byType[t].begin(), byType[t].end());

    vector<int> ord(K); for (int t = 1; t <= K; t++) ord[t - 1] = t;
    sort(ord.begin(), ord.end(), [&](int a, int b) { return byType[a].back() < byType[b].back(); });
    vector<int> posOf(K + 1); vector<ll> B(K);
    for (int i = 0; i < K; i++) { posOf[ord[i]] = i; B[i] = byType[ord[i]].back(); }

    vector<pair<int,int>> fish(F);
    for (int i = 0; i < F; i++) fish[i] = {L[i], T[i]};
    sort(fish.begin(), fish.end());

    vector<ll> m(K, 0), val(K, 1 % MOD);        // val[p] = m[p] + 1 คือจำนวนทางเลือกของชนิดนั้น
    int ptr = 0; ll ans = 0;
    for (int i = 0; i < K; i++) {
        while (ptr < F && 2LL * fish[ptr].first <= B[i]) {
            int p = posOf[fish[ptr].second]; m[p]++; val[p] = (m[p] + 1) % MOD; ptr++;
        }
        ll left = 1 % MOD;
        for (int j = 0; j < i; j++) left = left * val[j] % MOD;     // คูณไล่แทนการถามเซกเมนต์ทรี
        ans = (ans + m[i] % MOD * left) % MOD;

        const vector<int>& v = byType[ord[i]];
        int xi = (int)(lower_bound(v.begin(), v.end(), B[i] / 2 + 1) - v.begin());
        int J = (int)(lower_bound(B.begin(), B.end(), 2LL * v[xi]) - B.begin());
        if (J < i + 1) J = i + 1;
        ll mid = 1 % MOD;
        for (int j = i + 1; j < J; j++) mid = mid * val[j] % MOD;
        ans = (ans + left * mid) % MOD;
    }
    printf("%lld\n", ans % MOD);
}

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

ผมสุ่มทะเลสาบเล็ก ๆ ที่มีปลาไม่เกินเจ็ดตัวและอัญมณีไม่เกินสี่ประเภท จำนวน 4,000 ชุด แล้วเทียบสามอย่างคือ โค้ดเต็ม โค้ดชุดทดสอบย่อย และตัวไล่นับชุดทุกชุดแบบซื่อ ๆ ตรงกันหมด ตัวไล่นับตรง ๆ นี่แหละที่ทำให้กล้าเชื่อเรื่องการกันนับซ้ำ เพราะมันใช้ set เก็บชุด ซึ่งกันซ้ำให้เองโดยไม่ต้องมีทฤษฎีอะไรเลย

บิลค่าใช้จ่าย · เทียบสองทางด้วยหน่วยเดียวกัน
ทางขอบเขตที่มันไหวงานคร่าว ๆ
คูณไล่ตรง ๆ ทุกรอบ K ไม่เกิน 7,000 49,000,000 ครั้ง
เซกเมนต์ทรีที่เก็บผลคูณ K ถึง 500,000 9,500,000 ครั้ง
สองแถวนี้นับด้วยหน่วยเดียวกันคือจำนวนครั้งของการคูณหนึ่งครั้ง ที่ขอบเขตของตัวเอง งานของสองทางใกล้กันกว่าที่คิด แต่ถ้าเอาทางบนไปรันที่ K ห้าแสน มันจะกลายเป็นสองแสนห้าหมื่นล้านครั้งทันที ซึ่งคือส่วนที่ตารางนี้อธิบายไม่ได้ในบรรทัดเดียว จุดสำคัญคือทางบนโตแบบยกกำลังสอง ส่วนทางล่างโตแบบเชิงเส้นคูณลอการิทึม

โค้ด C++

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

ดูโค้ดเต็ม
fish.cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int SZ; ll MOD; vector<ll> seg;                 // เซกเมนต์ทรีเก็บ "ผลคูณ" เพราะ M ไม่ใช่จำนวนเฉพาะ จึงหารกลับไม่ได้
void build(int k){ SZ=1; while(SZ<k) SZ<<=1; seg.assign(2*SZ,1%MOD); }
void setv(int p,ll v){ p+=SZ; seg[p]=v%MOD; for(p>>=1;p;p>>=1) seg[p]=seg[2*p]*seg[2*p+1]%MOD; }
ll prod(int l,int r){ if(l>r) return 1%MOD; ll a=1%MOD,b=1%MOD; for(l+=SZ,r+=SZ+1;l<r;l>>=1,r>>=1){ if(l&1) a=a*seg[l++]%MOD; if(r&1) b=seg[--r]*b%MOD; } return a*b%MOD; }
int main(){
    int F,K; if(scanf("%d %d %lld",&F,&K,&MOD)!=3) return 0;
    vector<int> L(F),T(F);
    vector<vector<int>> byType(K+1);
    for(int i=0;i<F;i++){ scanf("%d %d",&L[i],&T[i]); byType[T[i]].push_back(L[i]); }
    for(int t=1;t<=K;t++) sort(byType[t].begin(),byType[t].end());
    vector<int> ord; ord.reserve(K);
    for(int t=1;t<=K;t++) ord.push_back(t);
    sort(ord.begin(),ord.end(),[&](int a,int b){ return byType[a].back()<byType[b].back(); });
    vector<int> posOf(K+1); vector<ll> B(K);
    for(int i=0;i<K;i++){ posOf[ord[i]]=i; B[i]=byType[ord[i]].back(); }

    vector<pair<int,int>> fish(F);               // (ความยาว, ชนิด) เรียงตามความยาว
    for(int i=0;i<F;i++) fish[i]={L[i],T[i]};
    sort(fish.begin(),fish.end());

    build(K);
    vector<ll> m(K,0);                           // m[p] = จำนวนปลาชนิด ord[p] ที่อยู่ในคำนำหน้าปัจจุบัน
    int ptr=0; ll ans=0;
    for(int i=0;i<K;i++){
        while(ptr<F && 2LL*fish[ptr].first<=B[i]){ int p=posOf[fish[ptr].second]; m[p]++; setv(p,(m[p]+1)%MOD); ptr++; }
        ll own=m[i];
        ll left=prod(0,i-1);
        ans=(ans+own%MOD*left)%MOD;              // กรณี (ข) นับชนิดตัวเองไม่เต็มเพดาน
        // กรณี (ก) นับชนิดตัวเองเต็มเพดาน  ต้องหา J = ชนิดแรกที่คำนำหน้ากลืนปลาตัว X เข้าไปได้
        const vector<int>& v=byType[ord[i]];
        ll bt=B[i];
        int xi=(int)(lower_bound(v.begin(),v.end(),bt/2+1)-v.begin());   // ตัวแรกที่ 2L > bt
        ll need=2LL*v[xi];
        int J=(int)(lower_bound(B.begin(),B.end(),need)-B.begin());
        if(J<i+1) J=i+1;
        ans=(ans+left*prod(i+1,J-1))%MOD;
    }
    printf("%lld\n",ans%MOD);
}

เวลาที่วัดได้บนเครื่องผม ปลา 500,000 ตัว อัญมณี 200,000 ประเภท ใช้เวลา 464 มิลลิวินาที จากลิมิตสามวินาที

เซกเมนต์ทรีแบบที่ข้อนี้ใช้ รวมถึงเวอร์ชันที่มีการอัปเดตทั้งช่วง อยู่ในบทปูพื้นฐาน เซกเมนต์ทรีกับการค้นหาคำตอบแบบไบนารี

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

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

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

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

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

แหล่งที่มา

  1. โจทย์ Fish บน programming.in.th ข้อ 2010 programming.in.th/tasks/2010 (สืบค้น 6 กันยายน 2026)
  2. ต้นทางของโจทย์คือ 20th International Olympiad in Informatics ที่กรุงไคโร ประเทศอียิปต์ วันแข่งที่ 1