[0]zero-index clytie0x/zero-index

25.6 แบบฝึกหัด

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

ข้อ 1 เป้ตั้งแคมป์

ระดับง่าย เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N และ W คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 100 และ 1 ≤ W ≤ 100000

อีก N บรรทัด บรรทัดที่ i มีจำนวนเต็ม wi และ vi โดยที่ 1 ≤ wi ≤ 100000 และ 1 ≤ vi ≤ 109

ข้อมูลออก

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

5 10
5 60
4 40
6 70
3 30
2 15
110
3 10
6 30
5 20
5 20
40
ดูคำใบ้

ใช้ best แถวเดียวขนาด W + 1 ตามหน้า 25.1 แต่ละชิ้นวน c ถอยหลังจาก W ลงมาถึง wi เพราะแต่ละชิ้นมีชิ้นเดียว ถ้าวนเดินหน้าจะหยิบชิ้นเดิมซ้ำ คะแนนรวมได้ถึง 1011 ต้องเก็บเป็น long long ลองทุกชุดด้วย backtracking มี 2100 ชุด ไม่ทันแน่นอน ส่วน DP ใช้ราว 107 ขั้น

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, capacity;
    cin >> n >> capacity;
    vector<long long> best(capacity + 1, 0);
    for (int i = 0; i < n; i++) {
        int weight;
        long long value;
        cin >> weight >> value;
        for (int c = capacity; c >= weight; c--) {
            best[c] = max(best[c], best[c - weight] + value);
        }
    }
    cout << best[capacity] << '\n';
    return 0;
}

ข้อ 2 ตัดผ้าขาย

ระดับง่าย เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N และ L คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 100 และ 1 ≤ L ≤ 100000

อีก N บรรทัด บรรทัดที่ i มีจำนวนเต็ม li และ pi โดยที่ 1 ≤ li ≤ 100000 และ 1 ≤ pi ≤ 106

ข้อมูลออก

รายได้มากที่สุดเป็นบาท

3 12
2 30
5 80
7 120
200
3 10
3 40
10 100
7 75
120
ดูคำใบ้

แต่ละแบบตัดซ้ำได้ไม่จำกัด เป็น unbounded knapsack ในหน้า 25.1 ใช้ best[c] คือรายได้มากสุดจากผ้ายาว c เซนติเมตร แต่ละแบบวน c เดินหน้า จาก li ถึง L เพื่อให้ตัดแบบเดิมซ้ำได้ เศษที่เหลือไม่ต้องคิดอะไรเพิ่ม เพราะ best เริ่มเป็น 0 ทุกช่องอยู่แล้ว รายได้ถึง 1011 ใช้ long long ถ้าลองทุกจำนวนชิ้นของแต่ละแบบ แบบที่ยาว 1 เซนติเมตรแบบเดียวก็ต้องลองถึงราว 5 × 109 ครั้ง

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, length;
    cin >> n >> length;
    vector<long long> best(length + 1, 0);
    for (int i = 0; i < n; i++) {
        int piece;
        long long price;
        cin >> piece >> price;
        for (int c = piece; c <= length; c++) {
            best[c] = max(best[c], best[c - piece] + price);
        }
    }
    cout << best[length] << '\n';
    return 0;
}

ข้อ 3 สวนน้ำท่วม

ระดับง่าย เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 1000

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

ข้อมูลออก

จำนวนเส้นทาง mod 1000000007 ถ้าไม่มีเส้นทางเลยให้ตอบ 0

3 4
....
.#..
....
4
3 3
..#
##.
...
0
ดูคำใบ้

ทำตามโปรแกรม grid-blocked ในหน้า 25.4 จำนวนเส้นทางถึงช่องหนึ่งคือของช่องบนบวกของช่องซ้าย ช่องที่ท่วมเป็น 0 ระวังขอบ ช่องในแถวแรกที่อยู่ทางขวาของช่องที่ท่วมไปไม่ถึงแล้ว ห้ามตั้งเป็น 1 และถ้าช่องเริ่มต้นท่วมคำตอบคือ 0 mod ทุกช่อง ถ้าเดินทุกเส้นทางด้วย recursion ตาราง 1000 × 1000 มีเส้นทางมากกว่า 10500 เส้น

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1000000007;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int rows, cols;
    cin >> rows >> cols;
    vector<long long> ways(cols, 0);
    ways[0] = 1;
    for (int r = 0; r < rows; r++) {
        string line;
        cin >> line;
        for (int c = 0; c < cols; c++) {
            if (line[c] == '#') {
                ways[c] = 0;
            } else if (c > 0) {
                ways[c] = (ways[c] + ways[c - 1]) % MOD;
            }
        }
    }
    cout << ways[cols - 1] << '\n';
    return 0;
}

ข้อ 4 เทียบสาย DNA

ระดับง่าย เวลาไม่เกิน 1 วินาที

นักวิจัยเทียบสาย DNA ของข้าวสองสายพันธุ์ สาย DNA เขียนเป็นข้อความที่มีแค่ตัวอักษร A C G T วิธีวัดความใกล้ชิดแบบหนึ่ง คือหาลำดับย่อยร่วมที่ยาวที่สุดของสองสาย ลำดับย่อยได้จากการลบตัวอักษรบางตัวออก (หรือไม่ลบเลย) โดยไม่สลับลำดับตัวที่เหลือ ให้หาความยาวของลำดับย่อยร่วมที่ยาวที่สุด

ข้อมูลเข้า

สองบรรทัด บรรทัดละหนึ่งสาย DNA ยาว N และ M ตัวตามลำดับ โดยที่ 1 ≤ N, M ≤ 5000

ข้อมูลออก

ความยาวของลำดับย่อยร่วมที่ยาวที่สุด

GATTACA
TACGTA
4
AAAA
CCC
0
ดูคำใบ้

เป็น LCS ในหน้า 25.3 ตรงๆ ถ้าตัวสุดท้ายตรงกันได้ช่องทแยงบวกหนึ่ง ถ้าไม่ตรงได้ค่ามากกว่าของช่องบนกับช่องซ้าย ระวังตารางเริ่มที่ 1 แต่สตริงเริ่มที่ 0 ต้องเทียบ s[i - 1] กับ t[j - 1] ตารางเต็ม 5001 × 5001 ช่องใช้ราว 100 MB โจทย์ถามแค่ความยาว จึงเก็บสองแถวพอ recursion ที่ไม่จดคำตอบยาวแค่ 16 ตัวก็เกินวินาทีแล้ว

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s, t;
    cin >> s >> t;
    int n = s.size(), m = t.size();
    vector<int> above(m + 1, 0), row(m + 1, 0);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i - 1] == t[j - 1]) {
                row[j] = above[j - 1] + 1;
            } else {
                row[j] = max(above[j], row[j - 1]);
            }
        }
        swap(above, row);
    }
    cout << above[m] << '\n';
    return 0;
}

ข้อ 5 ค่าที่สูงขึ้นเรื่อยๆ

ระดับง่าย เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 200000

บรรทัดที่สองมีจำนวนเต็ม N ตัว คือค่าที่อ่านได้ตามลำดับเวลา แต่ละค่าอยู่ระหว่าง -109 ถึง 109

ข้อมูลออก

จำนวนครั้งที่เลือกได้มากที่สุด

8
3 1 4 1 5 9 2 6
4
7
7 7 8 8 8 9 3
3
ดูคำใบ้

เป็น LIS ในหน้า 25.2 N ถึงสองแสน วิธี O(N2) ต้องเทียบราว 2 × 1010 คู่ ไม่ทัน ใช้อาเรย์ tails กับ lower_bound แบบ O(N log N) ต้องมากกว่าจริงๆ จึงใช้ lower_bound ถ้าใช้ upper_bound ค่าที่เท่ากันจะถูกนับต่อกัน ลองกับข้อมูลที่ทุกตัวเท่ากัน คำตอบต้องเป็น 1

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> tails;
    for (int i = 0; i < n; i++) {
        int value;
        cin >> value;
        auto it = lower_bound(tails.begin(), tails.end(), value);
        if (it == tails.end()) {
            tails.push_back(value);
        } else {
            *it = value;
        }
    }
    cout << tails.size() << '\n';
    return 0;
}

ข้อ 6 แก้คำที่พิมพ์ผิด

ระดับกลาง เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

สองบรรทัด บรรทัดละหนึ่งคำ ประกอบด้วยตัวอักษรภาษาอังกฤษตัวเล็ก ยาว N และ M ตัวตามลำดับ โดยที่ 1 ≤ N, M ≤ 2000

ข้อมูลออก

จำนวนครั้งน้อยที่สุด

nakornpathom
nakhonpathom
2
kanchanaburi
kanjanaburi
2
ดูคำใบ้

เป็น edit distance ในหน้า 25.3 cost[i][j] คือจำนวนครั้งน้อยสุดที่แก้ i ตัวแรกของคำแรกให้เป็น j ตัวแรกของคำที่สอง ตัวสุดท้ายตรงกันได้ช่องทแยง ไม่ตรงได้ 1 บวกค่าน้อยสุดของช่องทแยง (เปลี่ยน) ช่องบน (ลบ) และช่องซ้าย (แทรก) ขอบไม่ใช่ 0 แบบ LCS cost[i][0] = i และ cost[0][j] = j ถ้าใช้สองแถว ช่อง 0 ของแถวใหม่ต้องตั้งเป็น i ทุกแถว

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s, t;
    cin >> s >> t;
    int n = s.size(), m = t.size();
    vector<int> above(m + 1), row(m + 1);
    for (int j = 0; j <= m; j++) {
        above[j] = j;
    }
    for (int i = 1; i <= n; i++) {
        row[0] = i;
        for (int j = 1; j <= m; j++) {
            if (s[i - 1] == t[j - 1]) {
                row[j] = above[j - 1];
            } else {
                row[j] = 1 + min({above[j - 1], above[j], row[j - 1]});
            }
        }
        swap(above, row);
    }
    cout << above[m] << '\n';
    return 0;
}

ข้อ 7 เส้นทางเก็บมะม่วง

ระดับกลาง เวลาไม่เกิน 1 วินาที

สวนมะม่วงแบ่งเป็นตาราง R แถว C คอลัมน์ ช่องแถว r คอลัมน์ c มีคะแนน ar,c ช่องที่มีมะม่วงสุกเป็นค่าบวก ช่องที่มีรังมดแดงเป็นค่าติดลบ น้องเดินจากช่องมุมซ้ายบนไปช่องมุมขวาล่าง ก้าวหนึ่งไปทางขวา (R) หรือลงล่าง (D) และได้คะแนนของทุกช่องที่เดินผ่าน รวมช่องแรกกับช่องสุดท้าย ให้หาคะแนนรวมที่มากที่สุด และเส้นทางที่ได้คะแนนนั้น

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 1000 และ R + C ≥ 3

อีก R บรรทัด บรรทัดที่ r มีจำนวนเต็ม C ตัว คือ ar,1 ถึง ar,C โดยที่ -109 ≤ ar,c ≤ 109

ข้อมูลออก

บรรทัดแรกคือคะแนนรวมที่มากที่สุด

บรรทัดที่สองคือเส้นทาง เป็นข้อความยาว R + C - 2 ตัว ประกอบด้วย D กับ R

3 4
3 1 0 7
4 8 2 1
0 6 9 3
33
DRDRR
2 3
1 2 0
2 0 0
3
DRR
ดูคำใบ้

ย้อนรอยจากช่องขวาล่างแบบโปรแกรม mango-path ในหน้า 25.4 ไม่พอ เพราะการเลือกตอนเสมอจากท้ายไม่ได้ให้ข้อความที่มาก่อนเมื่ออ่านจากหน้า ให้คิดตารางกลับทิศ to_end[r][c] คือคะแนนมากสุดจากช่องนี้ไปถึงช่องขวาล่าง (รวมช่องนี้) เติมจากแถวล่างขึ้นบน ขวาไปซ้าย แล้วเดินจากช่องซ้ายบนไปข้างหน้า ทุกก้าวเลือก D ถ้าเดินลงได้และ to_end ของช่องล่างไม่น้อยกว่าของช่องขวา ระวังขอบด้านล่างกับด้านขวาที่มีทางเดียว และค่าติดลบ คะแนนรวมเกิน int

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int rows, cols;
    cin >> rows >> cols;
    vector<vector<long long>> to_end(rows, vector<long long>(cols));
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            cin >> to_end[r][c];
        }
    }
    for (int r = rows - 1; r >= 0; r--) {
        for (int c = cols - 1; c >= 0; c--) {
            if (r == rows - 1 && c == cols - 1) {
                continue;
            }
            if (r == rows - 1) {
                to_end[r][c] += to_end[r][c + 1];
            } else if (c == cols - 1) {
                to_end[r][c] += to_end[r + 1][c];
            } else {
                to_end[r][c] += max(to_end[r + 1][c], to_end[r][c + 1]);
            }
        }
    }
    string path;
    int r = 0, c = 0;
    while (r < rows - 1 || c < cols - 1) {
        if (c == cols - 1 || (r < rows - 1 && to_end[r + 1][c] >= to_end[r][c + 1])) {
            path += 'D';
            r++;
        } else {
            path += 'R';
            c++;
        }
    }
    cout << to_end[0][0] << '\n' << path << '\n';
    return 0;
}

ข้อ 8 ซ้อนกล่องของขวัญ

ระดับยาก เวลาไม่เกิน 1 วินาที

ร้านของขวัญมีกล่องกระดาษ N ใบ ใบที่ i มีฐานกว้าง wi มิลลิเมตร ยาว hi มิลลิเมตร ทุกใบสูงเท่ากันและเปิดฝาด้านบน กล่อง A ใส่ลงในกล่อง B ได้เมื่อ wA < wB และ hA < hB ทั้งคู่ กล่องมีลายด้านหน้าจึงห้ามหมุนสลับด้านกว้างกับด้านยาว ร้านอยากซ้อนกล่องเป็นชุดเดียวให้ได้มากใบที่สุด แบบที่ใบเล็กอยู่ในใบใหญ่กว่าต่อกันเป็นชั้นๆ ให้หาว่าซ้อนได้มากที่สุดกี่ใบ

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 100000

อีก N บรรทัด บรรทัดที่ i มีจำนวนเต็ม wi และ hi โดยที่ 1 ≤ wi, hi ≤ 109 กล่องสองใบอาจมีขนาดเท่ากัน

ข้อมูลออก

จำนวนกล่องที่ซ้อนกันได้มากที่สุด

5
50 40
60 70
30 50
60 45
20 20
3
4
10 1
10 2
10 3
10 4
1
ดูคำใบ้

เรียงกล่องตามความกว้างจากน้อยไปมาก แล้วชุดที่ซ้อนกันได้ต้องเป็นลำดับย่อยที่ความยาวเพิ่มขึ้นจริงๆ จึงกลายเป็น LIS ในหน้า 25.2 ปัญหาอยู่ที่กล่องที่กว้างเท่ากัน ซึ่งซ้อนกันไม่ได้ แต่ LIS อาจเลือกหลายใบที่กว้างเท่ากันถ้าความยาวเรียงเพิ่มขึ้น วิธีแก้คือเมื่อกว้างเท่ากันให้เรียงความยาว จากมากไปน้อย กล่องที่กว้างเท่ากันจะเลือกได้ไม่เกินหนึ่งใบเพราะความยาวลดลง แล้วทำ LIS แบบเพิ่มขึ้นจริงๆ บนความยาวด้วย lower_bound วิธี O(N2) ต้องเทียบราว 5 × 109 คู่ ไม่ทัน

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<pair<int, int>> boxes(n);
    for (int i = 0; i < n; i++) {
        cin >> boxes[i].first >> boxes[i].second;
    }
    sort(boxes.begin(), boxes.end(), [](const pair<int, int> &a, const pair<int, int> &b) {
        if (a.first != b.first) {
            return a.first < b.first;
        }
        return a.second > b.second;
    });
    vector<int> tails;
    for (int i = 0; i < n; i++) {
        auto it = lower_bound(tails.begin(), tails.end(), boxes[i].second);
        if (it == tails.end()) {
            tails.push_back(boxes[i].second);
        } else {
            *it = boxes[i].second;
        }
    }
    cout << tails.size() << '\n';
    return 0;
}

ข้อ 9 เชื่อมท่อ

ระดับยาก เวลาไม่เกิน 1 วินาที

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 500

บรรทัดที่สองมีจำนวนเต็ม N ตัว คือ a1 ถึง aN โดยที่ 1 ≤ ai ≤ 106

ข้อมูลออก

ค่าแก๊สรวมที่น้อยที่สุด ถ้ามีท่อท่อนเดียวไม่ต้องเชื่อม ให้ตอบ 0

4
40 10 30 20
200
1
7
0
ดูคำใบ้

เป็น DP ช่วงแบบหน้า 25.5 แต่แบ่งที่ "ครั้งสุดท้าย" แบบคูณเมทริกซ์ลูกโซ่ ให้ cost[i][j] คือค่าแก๊สน้อยสุดที่เชื่อมท่อนที่ i ถึง j ให้เป็นท่อนเดียว การเชื่อมครั้งสุดท้ายของช่วงนี้ต้องเชื่อมท่อนที่มาจาก i ถึง k กับท่อนที่มาจาก k + 1 ถึง j ครั้งนั้นเสียความยาวรวมของ i ถึง j เสมอ ไม่ว่า k คืออะไร หาความยาวรวมด้วย prefix sum เติมตามความยาวช่วงจากสั้นไปยาว เป็น O(N3) ระวังเชื่อมท่อนสั้นสุดก่อนเสมอแบบ greedy ไม่ถูก และค่ารวมเกิน int ถ้าลองทุกลำดับการเชื่อม N = 500 มีลำดับมากกว่า 101000 แบบ

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<long long> prefix(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        long long length;
        cin >> length;
        prefix[i] = prefix[i - 1] + length;
    }
    vector<vector<long long>> cost(n + 2, vector<long long>(n + 2, 0));
    for (int span = 2; span <= n; span++) {
        for (int i = 1; i + span - 1 <= n; i++) {
            int j = i + span - 1;
            long long best = LLONG_MAX;
            for (int k = i; k < j; k++) {
                best = min(best, cost[i][k] + cost[k + 1][j]);
            }
            cost[i][j] = best + prefix[j] - prefix[i - 1];
        }
    }
    cout << cost[1][n] << '\n';
    return 0;
}

ข้อ 10 รถส่งนมรอบเช้า

ระดับยาก เวลาไม่เกิน 1 วินาที

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

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

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 16 และ 0 ≤ K ≤ N - 1

อีก N บรรทัด บรรทัดที่ i (นับจาก 0) มีจำนวนเต็ม N ตัว ตัวที่ j คือระยะของถนนตรงจากจุด i ไปจุด j เป็นเมตร ระยะจากจุดหนึ่งไปตัวเองเป็น 0 ระยะอื่นอยู่ระหว่าง 1 ถึง 106

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

ข้อมูลออก

ระยะทางรวมที่สั้นที่สุด ถ้าไม่มีร้านค้าเลย ให้ตอบ 0

4 0
0 4 8 5
4 0 3 7
8 3 0 2
5 7 2 0
14
4 1
0 4 8 5
4 0 3 7
8 3 0 2
5 7 2 0
2
21
ดูคำใบ้

N ไม่เกิน 16 เป็นสัญญาณของ DP บน bitmask ให้ best[mask][last] คือระยะสั้นที่สุดที่ส่งครบจุดใน mask แล้วอยู่ที่ last ส่วนที่ต้องคิดเพิ่มคือเงื่อนไขร้านด่วน เก็บร้านด่วนทั้งหมดเป็น mask หนึ่งตัว ตอนจะต่อไปร้าน next ที่ไม่ใช่ร้านด่วน ต้องเช็กก่อนว่า mask ปัจจุบันมีร้านด่วนครบแล้ว ส่วนร้านด่วนต่อได้เสมอ ตอนจบต้องบวกระยะจาก last กลับจุด 0 ตามทิศ dist[last][0] และถ้า N เป็น 1 คำตอบคือ 0 ห้ามหาทางสั้นสุดระหว่างทุกคู่ก่อน เพราะโจทย์ไม่ให้อ้อม ลองทุกลำดับร้านมี 15! ราว 1.3 × 1012 แบบ ส่วน bitmask มีไม่ถึง 2 × 107 ขั้น

ดูเฉลย
#include <bits/stdc++.h>
using namespace std;

const int INF = 1000000000;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, k;
    cin >> n >> k;
    vector<vector<int>> dist(n, vector<int>(n));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> dist[i][j];
        }
    }
    int urgent = 0;
    for (int i = 0; i < k; i++) {
        int shop;
        cin >> shop;
        urgent |= 1 << shop;
    }
    if (n == 1) {
        cout << 0 << '\n';
        return 0;
    }
    int full = (1 << n) - 1;
    vector<vector<int>> best(1 << n, vector<int>(n, INF));
    best[1][0] = 0;
    for (int mask = 1; mask <= full; mask++) {
        bool urgent_done = (mask & urgent) == urgent;
        for (int last = 0; last < n; last++) {
            if (best[mask][last] == INF) {
                continue;
            }
            for (int next = 0; next < n; next++) {
                if ((mask & (1 << next)) != 0) {
                    continue;
                }
                if ((urgent & (1 << next)) == 0 && !urgent_done) {
                    continue;
                }
                int grown = mask | (1 << next);
                best[grown][next] = min(best[grown][next], best[mask][last] + dist[last][next]);
            }
        }
    }
    int answer = INF;
    for (int last = 1; last < n; last++) {
        answer = min(answer, best[full][last] + dist[last][0]);
    }
    cout << answer << '\n';
    return 0;
}