ปูพื้นฐาน

นับก่อน แล้วค่อยเดิน: หาตัวที่ R ในกองที่ใหญ่เกินจะสร้างจริง

โจทย์ที่ขอ "ตัวที่ R ตามพจนานุกรม" กับโจทย์ที่ขอ "อันดับของตัวนี้" เป็นโค้ดชุดเดียวกันที่ต่างกันแค่ทิศทาง บทนี้ให้แม่พิมพ์นั้น พร้อมสองบักที่เกิดกับทุกคนอย่างน้อยครั้งหนึ่ง

บทปูพื้นฐาน ★★☆☆☆ dpcountingพื้นฐาน อ่าน 11 นาที 7 กันยายน 2026

ปัญหาที่บทนี้แก้

โจทย์ตระกูลนี้ถามอยู่สองแบบ ซึ่งเป็นคำถามกลับด้านกัน

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

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

ขั้นที่ 1 นับ

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

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

ขั้นที่ 2 เดิน

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

ระวัง

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

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

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

ลองเอง · เดินไปหาสตริงอันดับที่ขอ

ยาวสี่หลัก ลองไล่ดูก่อนว่ามีกี่แบบ

ขอสตริงยาว 4 หลักที่ไม่มีเลขหนึ่งติดกัน อันดับที่ 5 จากทั้งหมด 8 แบบ

วางทีละหลัก ห้ามให้เลขหนึ่งอยู่ติดกัน

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

เรียงตามพจนานุกรม เลขศูนย์มาก่อนเลขหนึ่ง

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

โจทย์ฝึกข้อที่ 1 · สตริงฐานสองที่ไม่มีเลขหนึ่งติดกัน ตัวที่ R

หาสตริงฐานสองยาว n ที่ไม่มีเลข 1 อยู่ติดกัน ตัวที่ R ตามลำดับพจนานุกรม (โดย 0 มาก่อน 1) ถ้ามีไม่ถึง R ตัวให้ตอบ -1

EXAMPLE
InputOutput
6 9010000

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

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

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

สตริงยาว 6 ที่ไม่มีเลขหนึ่งติดกัน ทั้งหมด 21 แบบ 1. 000000 2. 000001 3. 000010 4. 000100 5. 000101 6. 001000 7. 001001 8. 001010 9. 010000 10. 010001 11. 010010 12. 010100 13. 010101 14. 100000 15. 100001 16. 100010 17. 100100 18. 100101 19. 101000 20. 101001 21. 101010
รายการทั้งหมดของตัวอย่างนี้ เรียงตามพจนานุกรมจริง ตัวสีเขียวคืออันดับที่โจทย์ขอ ของจริงจะไล่แบบนี้ไม่ได้ เพราะรายการโตเร็วเกินไป

ใบ้

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

เฉลยข้อที่ 1

ที่มาของท่านี้ · สองบักที่เกิดกับทุกคน

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

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

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

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

สถานะคือตัวก่อนหน้า มีสองค่า สูตรจึงสั้นมาก ถ้าตัวก่อนเป็น 0 ตัวนี้ใส่ได้ทั้งสองอย่าง ถ้าตัวก่อนเป็น 1 ตัวนี้ใส่ได้แค่ 0

จำนวนสตริงที่ถูกกติกาของแต่ละความยาว
12345678910
23581321345589144
นี่คือลำดับฟีโบนักชีที่เลื่อนไปสองตำแหน่ง ซึ่งเป็นสัญญาณดีว่าสูตรเราถูก และเป็นเหตุผลที่ไล่สร้างทุกตัวไม่ไหว ที่ n เท่ากับ 90 จำนวนของมันเกินขอบเขตของ จำนวนเต็ม 64 บิตไปแล้ว
เดินหาตัวที่ 9 ของความยาว 6
ตำแหน่งใส่ 0 แล้วมีตามมากี่ตัว ใส่ 1 แล้วมีตามมากี่ตัวเลือกR ที่เหลือ
0 13 8 0 9
1 8 5 1 1
2 5 0 1
3 3 2 0 1
4 2 1 0 1
5 1 1 0 1
ช่องว่างในคอลัมน์ที่สามคือตำแหน่งที่ใส่ 1 ไม่ได้ เพราะตัวก่อนหน้าเป็น 1 ผลลัพธ์คือ 010000 ตรงกับการไล่สร้างทั้ง 21 ตัวแล้วนับเอา
kth_no_adjacent.cpp
// สตริงฐานสองยาว n ที่ไม่มีเลข 1 ติดกัน  ตัวที่ R ตามลำดับพจนานุกรม
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int main() {
    int n; ull R;
    scanf("%d %llu", &n, &R);
    // f[i][last] = จำนวนวิธีเติมตำแหน่ง i..n-1 เมื่อตัวก่อนหน้าคือ last
    vector<array<ull,2>> f(n + 1);
    f[n][0] = f[n][1] = 1;
    for (int i = n - 1; i >= 0; i--) {
        f[i][0] = f[i + 1][0] + f[i + 1][1];   // ตัวก่อนเป็น 0 ใส่ได้ทั้ง 0 และ 1
        f[i][1] = f[i + 1][0];                 // ตัวก่อนเป็น 1 ใส่ได้แค่ 0
    }
    if (R > f[0][0]) { printf("-1\n"); return 0; }
    string out; int last = 0;
    for (int i = 0; i < n; i++) {
        for (int b = 0; b <= 1; b++) {         // ไล่ 0 ก่อน 1 ตามพจนานุกรม
            if (b == 1 && last == 1) continue;
            ull cnt = f[i + 1][b];
            if (R <= cnt) { out.push_back('0' + b); last = b; break; }
            R -= cnt;
        }
    }
    printf("%s\n", out.c_str());
}

ผมสุ่ม n กับ R จำนวน 800 ชุดเทียบกับตัวไล่สร้างทุกตัว ตรงกันหมด

โจทย์ฝึกข้อที่ 2 · อันดับของการเรียงสับเปลี่ยน

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

EXAMPLE
InputOutput
4
2 3 1 4
9

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

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

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

ชุดที่อยู่รอบ ๆ อันดับที่ถาม
อันดับการเรียงสับเปลี่ยน
6 1 4 3 2
7 2 1 3 4
8 2 1 4 3
9 2 3 1 4
10 2 3 4 1
ของจริง n ใหญ่จนไล่แบบนี้ไม่ได้ ท่าที่ใช้จริงคือเดินทีละตำแหน่งแล้วนับว่ามีชุดกี่ชุด ที่ถูกข้ามไป ซึ่งคือภาพถัดไป
นับชุดที่มาก่อน ทีละตำแหน่ง 2 เล็กกว่าและยังไม่ใช้ 1 ที่เหลือเรียงได้ 6 ข้ามไป 6 3 เล็กกว่าและยังไม่ใช้ 1 ที่เหลือเรียงได้ 2 ข้ามไป 2 1 เล็กกว่าและยังไม่ใช้ 0 ที่เหลือเรียงได้ 1 ข้ามไป 0 4 เล็กกว่าและยังไม่ใช้ 0 ที่เหลือเรียงได้ 1 ข้ามไป 0 ข้ามรวม 6 + 2 + 0 + 0 แล้วบวกหนึ่ง ได้ 9
เดินทีละตำแหน่ง ที่ตำแหน่งหนึ่ง ๆ ชุดที่ขึ้นต้นเหมือนกันแต่ตัวนี้เล็กกว่า จะมาก่อนทั้งหมด จำนวนนั้นคือตัวที่ยังไม่ถูกใช้และเล็กกว่า คูณด้วยจำนวนวิธีเรียงของที่เหลือ

ใบ้

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

เฉลยข้อที่ 2

ที่ตำแหน่งแรก ทุกชุดที่ขึ้นต้นด้วยเลขที่เล็กกว่า p[0] อยู่ก่อนหน้าชุดของเราทั้งหมด และแต่ละตัวเลือกนั้นมีวิธีเรียงของที่เหลือ (n − 1)! แบบ ก็บวกเข้าไปตามจำนวนตัวเลือก แล้วขยับไปตำแหน่งถัดไปโดยตัดเลขที่ใช้ไปแล้วออกจากกลุ่มที่นับ

permutation_rank.cpp
// อันดับของการเรียงสับเปลี่ยนตามพจนานุกรม  (สมาชิกคือ 1..n ไม่ซ้ำ)
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int main() {
    int n; scanf("%d", &n);
    vector<int> p(n);
    for (auto& v : p) scanf("%d", &v);
    vector<ull> fact(n + 1, 1);
    for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
    vector<char> used(n + 2, 0);
    ull rank_ = 1;
    for (int i = 0; i < n; i++) {
        int smaller = 0;                        // ตัวที่ยังไม่ถูกใช้และเล็กกว่า p[i]
        for (int v = 1; v < p[i]; v++) if (!used[v]) smaller++;
        rank_ += (ull)smaller * fact[n - i - 1];
        used[p[i]] = 1;
    }
    printf("%llu\n", rank_);
}

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

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

ผมสุ่มการเรียงสับเปลี่ยนขนาดเล็ก 600 ชุดเทียบกับการไล่ทุกชุดตามลำดับ ตรงกันหมด

อีกท่าหนึ่งของคำถามเดียวกัน · เมื่อของที่ต้องเรียงไม่มีลำดับตามธรรมชาติ

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

แต่มีโจทย์ "ขออันดับที่ k" อีกตระกูลที่ของไม่มีโครงแบบนั้นเลย เช่น ผลบวกของทุกคู่ในอาเรย์ ที่เล็กเป็นอันดับที่ k ผลบวกไม่ได้ประกอบขึ้นทีละหลัก มันเป็นเพียงตัวเลข และมีทั้งหมด n(n-1)/2 ตัว ซึ่งที่ n = 200,000 คือราวสองหมื่นล้านตัว สร้างทั้งกองไม่ได้

ท่าที่ใช้ตรงนี้คือค้นหาคำตอบแบบไบนารี คู่กับการนับ ซึ่งกลับทิศของคำถามทั้งหมด แทนที่จะถามว่า "อันดับที่ k คือค่าอะไร" ให้ถามว่า

ถ้าคำตอบคือ x จะมีของกี่ชิ้นที่ไม่เกิน x

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

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

kth_pair_sum.cpp
// ค้นหาคำตอบแบบไบนารีคู่กับการนับ: หาผลบวก a[i]+a[j] (i<j) ที่เล็กเป็นอันดับที่ k
// ไม่ต้องสร้างผลบวกทั้ง n(n-1)/2 ตัวเลย
// อินพุต: n k แล้วอาเรย์ n ตัว   เอาต์พุต: ค่าผลบวกอันดับที่ k (นับจาก 1)
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n; long long k;
    if (scanf("%d %lld", &n, &k) != 2) return 0;
    vector<long long> a(n);
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    sort(a.begin(), a.end());

    // นับว่ามีผลบวกกี่คู่ที่ไม่เกิน x ด้วยสองตัวชี้ ซึ่งเป็น O(n) ต่อการนับหนึ่งครั้ง
    auto countLE = [&](long long x) {
        long long c = 0;
        int j = n - 1;
        for (int i = 0; i < n; i++) {
            if (j <= i) j = i;                       // ไม่ให้ตัวชี้ขวาแซงมาซ้ายกว่าตัวชี้ซ้าย
            while (j > i && a[i] + a[j] > x) j--;
            if (j > i) c += j - i;                   // คู่ (i, i+1..j) ผ่านทั้งหมด
        }
        return c;
    };

    long long lo = a[0] + a[1], hi = a[n - 1] + a[n - 2];
    while (lo < hi) {
        long long mid = lo + (hi - lo) / 2;          // ระวังการปัดของเลขลบ ใช้รูปนี้จึงปลอดภัย
        if (countLE(mid) >= k) hi = mid;
        else lo = mid + 1;
    }
    printf("%lld\n", lo);
    return 0;
}
ตัวตรวจที่ทำให้กล้าเชื่อโค้ดข้างบน

ท่านี้มีจุดพลาดสองจุดที่คลาสสิกมาก จุดแรกคือขอบของการค้นหา ถ้าเขียนเงื่อนไขเป็น > แทน ≥ คำตอบจะคลาดไปหนึ่ง จุดที่สองคือการปัดของ mid เมื่อค่าเป็นเลขลบ โค้ดข้างบนจึงเขียน lo + (hi - lo) / 2 ไม่ใช่ (lo + hi) / 2 ตัวตรวจจึงต้องสร้างผลบวกทุกคู่จริง เรียง แล้วหยิบตัวที่ k ซึ่งไม่มีการค้นหาและไม่มีการนับเลย

brute_kth_sum.cpp
// ตัวตรวจอิสระ: สร้างผลบวกทุกคู่จริง เรียง แล้วหยิบตัวที่ k
// ไม่มีการค้นหาแบบไบนารี ไม่มีการนับ จึงไม่ได้ทดสอบข้ออ้างของท่านั้นเลย
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n; long long k;
    if (scanf("%d %lld", &n, &k) != 2) return 0;
    vector<long long> a(n);
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    vector<long long> all;
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++) all.push_back(a[i] + a[j]);
    sort(all.begin(), all.end());
    printf("%lld\n", all[k - 1]);
    return 0;
}

สุ่มอาเรย์เล็ก ๆ ที่มีทั้งค่าลบและค่าซ้ำ พร้อมสุ่ม k ให้ครอบทุกอันดับที่เป็นไปได้ แล้วเทียบ 700 รอบ ตรงกันทุกรอบ ค่าซ้ำสำคัญมาก เพราะเมื่อมีผลบวกหลายคู่ที่เท่ากัน ค่าที่อันดับ k กับจำนวนคู่ที่ไม่เกินค่านั้น จะไม่สอดคล้องกันแบบหนึ่งต่อหนึ่งอีก ซึ่งเป็นจุดที่โค้ดแบบนี้ชอบพลาด ที่ n = 200,000 โค้ดข้างบนใช้เวลา 0.08 วินาที

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

เอาไปใช้ที่ไหนในคลังนี้

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

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

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