Hướng dẫn giải của [200 đề thầy Đức] Bài 93: Tạo số


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Hướng dẫn giải

Cho $n$ chữ số, cần tách chúng thành hai số nguyên $a$ và $b$ (dùng hết mọi chữ số, được phép có số $0$ đứng đầu), sao cho $a + b$ nhỏ nhất. Ví dụ: với các chữ số $2, 3, 4, 5, 6, 8$ ta được $358 + 246 = 604$.

Sub 1: Vét cạn các cách chia độ dài — ~O(n^2)~, dùng khi ~n \le 12~

Số $a$ có $La$ chữ số và $b$ có $Lb = n - La$ chữ số, với $La$ chạy từ $0$ đến $n$. Với mỗi cách chia độ dài, tổng nhỏ nhất đạt được khi các chữ số nhỏ được đặt vào các cột có trọng số $10^p$ lớn hơn. Chọn cách chia cho tổng nhỏ nhất.

Nếu chỉ nộp sub này: đúng 21/21 test.

Sub 2: Chia đều độ dài — ~O(n \log n)~

Chia đều: $a$ có $\lceil n/2 \rceil$ chữ số, $b$ có $\lfloor n/2 \rfloor$ chữ số. Khi đó các trọng số $10^p$ được dùng ít nhất có thể, nên tổng nhỏ nhất. Với cách chia đều, thực hiện như sub 1: sắp các chữ số tăng dần, rồi đặt lần lượt vào các cột từ trọng số lớn xuống nhỏ.

Bẫy cần chú ý: $n$ đến $50$ nên tổng có thể lớn hơn $10^{25}$. Vì vậy chương trình không dùng số nguyên thường mà tính hệ số của từng cột rồi cộng có nhớ thành chuỗi chữ số.

Chương trình hoàn chỉnh

Chương trình đọc $T$ bộ dữ liệu, sắp xếp chữ số. Nếu mọi $n \le 12$ thì gọi sub1, ngược lại gọi sub2.

#include <bits/stdc++.h>
using namespace std;

int T;
vector<vector<int>> tests;   // mỗi bộ dữ liệu: n chữ số đã sắp xếp tăng dần

// Tổng của hai số có La và Lb chữ số (cho phép số 0 đứng đầu) khi chia các chữ số cho hai số.
// Cột thứ p (trọng số 10^p) có cnt chữ số. Đặt chữ số nhỏ vào cột có trọng số lớn trước
// (để tổng nhỏ nhất). Trả về tổng dưới dạng chuỗi (n <= 50 nên tổng có thể rất lớn).
string sumWithSplit(const vector<int> &dig, int La, int Lb) {
    int L = max(La, Lb);
    vector<long long> coef(L, 0);
    int idx = 0;
    for (int p = L - 1; p >= 0; p--) {
        int cnt = (p < La) + (p < Lb);     // số chữ số ở cột p
        for (int c = 0; c < cnt; c++) coef[p] += dig[idx++];
    }
    // cộng có nhớ để đổi các hệ số cột thành chuỗi thập phân
    vector<int> res;
    long long carry = 0;
    for (int p = 0; p < L; p++) {
        long long v = coef[p] + carry;
        res.push_back(v % 10);
        carry = v / 10;
    }
    while (carry) { res.push_back(carry % 10); carry /= 10; }
    while (res.size() > 1 && res.back() == 0) res.pop_back();
    string s;
    for (int i = (int)res.size() - 1; i >= 0; i--) s += char('0' + res[i]);
    return s;
}

// so sánh hai số dạng chuỗi (không có số 0 đứng đầu)
bool lessStr(const string &x, const string &y) {
    if (x.size() != y.size()) return x.size() < y.size();
    return x < y;
}

// Sub 1: vét cạn — thử mọi cách chia độ dài (La, Lb) với La + Lb = n, lấy tổng nhỏ nhất.
// Dùng khi mọi n <= 12
void sub1() {
    for (int t = 0; t < T; t++) {
        int n = tests[t].size();
        string best = "";
        bool has = false;
        for (int La = 0; La <= n; La++) {
            string cur = sumWithSplit(tests[t], La, n - La);
            if (!has || lessStr(cur, best)) { best = cur; has = true; }
        }
        cout << best << "\n";
    }
}

// Sub 2: chia đều: a có ceil(n/2) chữ số, b có floor(n/2) chữ số.
// Chia đều thì các trọng số 10^p xuất hiện ít nhất, nên tổng nhỏ nhất.
void sub2() {
    for (int t = 0; t < T; t++) {
        int n = tests[t].size();
        cout << sumWithSplit(tests[t], (n + 1) / 2, n / 2) << "\n";
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> T;
    tests.resize(T);
    bool small = true;
    for (int t = 0; t < T; t++) {
        int n;
        cin >> n;
        tests[t].assign(n, 0);
        for (int i = 0; i < n; i++) cin >> tests[t][i];
        sort(tests[t].begin(), tests[t].end());
        if (n > 12) small = false;
    }
    if (small) sub1();   // n nhỏ: vét cạn các cách chia
    else sub2();         // n lớn: chia đều
    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.