Hướng dẫn giải của Phân xưởng gốm 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.

Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.

Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.

Bạn phải đăng nhập để mở lời giải này.

Đăng nhập

Tác giả: Hiếu Nguyễn, tranhaian173, hai_codeer, mvu_ltz286

Editorial for pottery: Phân xưởng gốm sứ

Hiểu bài toán

Bài toán mô phỏng quá trình sản xuất gốm sứ trong một ngày làm việc với thời gian T. Có hai phân xưởng: phân xưởng nặn (N thợ thủ công, mỗi người nặn xong một sản phẩm trong thời gian ai) và phân xưởng vẽ (M thợ thủ công, mỗi người vẽ xong một sản phẩm trong thời gian bj).

Quy trình: Sáng hôm đó, tất cả sản phẩm được nặn xong và chuyển ngay (thời gian coi như 0) sang phân xưởng vẽ. Phân xưởng vẽ hoàn thành việc vẽ và mọi sản phẩm được đưa đi nung ngay khi hoàn thành.

Điều kiện: Phân xưởng nặn bắt đầu lúc t=0. Phân xưởng vẽ chỉ có thể bắt đầu khi phân xưởng nặn đã chuyển hàng sang. Vì chuyển hàng chỉ diễn ra một lần duy nhất sau khi phân xưởng nặn kết thúc công việc của ngày (tức là khi thời gian trôi qua đủ để nặn xong tất cả các sản phẩm được giao), nên thời gian để phân xưởng vẽ làm việc bị giới hạn lại. Cụ thể, nếu phân xưởng nặn dành x thời gian để nặn xong số lượng sản phẩm P, thì phân xưởng vẽ chỉ còn lại (T - x) thời gian để vẽ.

Mục tiêu: Tìm số lượng sản phẩm tối đa P sao cho cả hai phân xưởng đều có thể hoàn thành xong P sản phẩm trong ngày làm việc T.

Giả định: Phân xưởng nặn làm việc liên tục từ t=0 đến t=x, sau đó phân xưởng vẽ làm việc liên tục từ t=x đến t=T.

Các cách tiếp cận

Cách Tối ưu hóa thời gian phân xưởng nặn (Binary Search on Split Time)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 5;

ll t, n, m;
ll a[N], b[N];

// Tính số sản phẩm phân xưởng có thể làm được trong thời gian time_limit
ll count_products(ll time_limit, ll arr[], int k) {
    ll sum = 0;
    for (int i = 1; i <= k; i++) {
        sum += (time_limit / arr[i]);
    }
    return sum;
}

ll bin_search() {
    ll l = 0, r = t; // Khoảng thời gian phân xưởng nặn làm việc
    ll ans = 0;
    while (l <= r) {
        ll mid = (l + r) / 2;
        // mid là thời gian phân xưởng nặn hoạt động
        // T - mid là thời gian phân xưởng vẽ hoạt động
        ll sp_nan = count_products(mid, a, n);
        ll sp_ve = count_products(t - mid, b, m);

        // Số lượng sản phẩm hoàn thành là số ít hơn trong hai phân xưởng
        ll completed = min(sp_nan, sp_ve);
        ans = max(ans, completed);

        // Logic tìm kiếm:
        // Nếu phân xưởng nặn làm ra ít sản phẩm hơn (sp_nan < sp_ve),
        // ta cần tăng thời gian cho phân xưởng nặn (tăng mid).
        if (sp_nan < sp_ve) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return ans;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> t >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    cin >> m;
    for (int i = 1; i <= m; i++) cin >> b[i];
    cout << bin_search();
    return 0;
}
  • Time Complexity: O((N + M) log T)
  • Space Complexity: O(N + M)

Phương pháp này dựa trên nhận định rằng tồn tại một mốc thời gian 'x' (đầu vào của Binary Search) mà phân xưởng nặn dùng để nặn xong các sản phẩm. Phân xưởng vẽ sẽ dùng phần thời gian còn lại (T - x).

  1. Ta dùng Binary Search để tìm giá trị 'x' tối ưu trong khoảng [0, T].
  2. Với mỗi giá trị 'x' trong quá trình tìm kiếm, ta tính:
    • Số sản phẩm nặn được: nan = sum( x / a_i ).
    • Số sản phẩm vẽ được: ve = sum( (T - x) / b_j ).
  3. Số sản phẩm hoàn thành chung là min(nan, ve).
  4. Mục tiêu của Binary Search không phải là tìm ra giá trị x để nan == ve, mà là duyệt qua các giá trị x để tìm ra số lượng sản phẩm lớn nhất có thể.
  5. Hàm check (hay logic if/else trong vòng lặp) điều chỉnh hướng tìm kiếm dựa trên phân xưởng nào đang là bottleneck (ngẽn cổ chai). Nếu nặn chậm hơn, ta tăng thời gian cho nặn.
Cách Tối ưu hóa số lượng sản phẩm (Binary Search on Answer)
#include <bits/stdc++.h>
using namespace std;

#define ll long long

// Kiểm tra xem liệu có thể hoàn thành 'need' sản phẩm hay không
bool check(ll need, int n, int m, ll t, vector<int> &a, vector<int> &b) {
    // Bước 1: Tối thiểu hóa thời gian cần thiết để nặn 'need' sản phẩm
    // Dùng Binary Search để tìm thời gian t_nan nhỏ nhất thỏa mãn
    ll l = 0, r = t, t_nan = t + 1;
    while (l <= r) {
        ll mid = (l + r) / 2;
        ll sum = 0;
        for (int i = 0; i < n; i++) {
            sum += mid / a[i];
            if (sum >= need) break; // Tối ưu hóa tốc độ
        }
        if (sum >= need) {
            t_nan = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    if (t_nan > t) return false; // Ngay cả khi dùng hết T vẫn không đủ nặn

    // Bước 2: Kiểm tra xem phần thời gian còn lại có đủ vẽ không
    ll t_ve = t - t_nan;
    ll sum_ve = 0;
    for (int i = 0; i < m; i++) {
        sum_ve += t_ve / b[i];
        if (sum_ve >= need) return true;
    }
    return sum_ve >= need;
}

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    ll t;
    int n, m;
    cin >> t >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    cin >> m;
    vector<int> b(m);
    for (int i = 0; i < m; i++) cin >> b[i];

    ll l = 0, r = 2e9; // Giới hạn trên đủ lớn (hoặc tính max possible)
    ll ans = 0;
    while (l <= r) {
        ll mid = (l + r) / 2;
        if (check(mid, n, m, t, a, b)) {
            ans = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    cout << ans;
    return 0;
}
  • Time Complexity: O((N + M) log T * log T)
  • Space Complexity: O(N + M)

Phương pháp này tìm kiếm trực tiếp số lượng sản phẩm tối đa (k) bằng Binary Search.

  1. Ta giả sử có thể hoàn thành k sản phẩm và kiểm tra tính khả thi.
  2. Để hoàn thành k sản phẩm, phân xưởng nặn cần tối thiểu bao nhiêu thời gian?
    • Dùng Binary Search trong khoảng [0, T] để tìm thời gian t_nan nhỏ nhất sao cho sum(t_nan / a_i) >= k.
  3. Nếu t_nan > T thì không thể hoàn thành k sản phẩm.
  4. Nếu không, lấy thời gian còn lại t_ve = T - t_nan. Kiểm tra xem với thời gian này, phân xưởng vẽ có vẽ được k sản phẩm không: sum(t_ve / b_j) >= k.
  5. Nếu cả hai đều thỏa mãn thì k là số sản phẩm có thể làm được, ta thử tìm số lớn hơn.
Cách Đơn giản hóa tham số
// Chỉ mang tính chất gợi ý, không phải code tối ưu hoàn toàn
// Áp dụng khi N, M rất nhỏ (ví dụ N, M <= 10)
// Duyệt qua các thời điểm chia tách (split point)
#include <bits/stdc++.h>
using namespace std;

int main() {
    long long T; int N, M;
    cin >> T >> N;
    vector<long long> a(N);
    for(int i=0; i<N; i++) cin >> a[i];
    cin >> M;
    vector<long long> b(M);
    for(int i=0; i<M; i++) cin >> b[i];

    long long max_prod = 0;

    // Chỉ duyệt các thời điểm mà phân xưởng nặn hoàn thành một sản phẩm
    // Hoặc các thời điểm quan trọng (thời gian chia hết cho a_i)
    vector<long long> important_times;
    important_times.push_back(0);
    important_times.push_back(T);

    for (long long x : a) {
        for (long long k = 1; k * x <= T; k++) {
            important_times.push_back(k * x);
            if (k * x + 1 <= T) important_times.push_back(k * x + 1);
        }
    }

    sort(important_times.begin(), important_times.end());
    important_times.erase(unique(important_times.begin(), important_times.end()), important_times.end());

    for (long long split : important_times) {
        if (split > T) break;
        long long cnt_nan = 0;
        for (long long x : a) cnt_nan += split / x;

        long long cnt_ve = 0;
        for (long long y : b) cnt_ve += (T - split) / y;

        max_prod = max(max_prod, min(cnt_nan, cnt_ve));
    }

    cout << max_prod << endl;
    return 0;
}
  • Time Complexity: Rất lớn, phụ thuộc vào T và a_i
  • Space Complexity: O(N + M)

Đây là cách tiếp cận 'thuần túy' hơn là một thuật toán tối ưu.

  1. Thay vì dùng Binary Search để tìm thời gian chia tách tối ưu, ta nhận thấy hàm số min(sum(T_split), sum(T-T_split)) là hàm bậc thang.
  2. Các bước nhảy của hàm số này xảy ra tại các thời điểm mà một trong các thợ thủ công hoàn thành một sản phẩm.
  3. Do đó, ta có thể liệt kê tất cả các thời điểm 'split time' có thể là điểm tối ưu (ví dụ: các bội số của a_i).
  4. Với mỗi thời điểm đó, ta tính số lượng sản phẩm và cập nhật kết quả.

Lưu ý: Cách này chỉ khả thi khi T nhỏ hoặc N, M rất nhỏ. Với T lên tới 10^9, số lượng thời điểm cần xét là vô cùng lớn.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O((N + M) log T) O(N + M) Tối ưu hóa thời gian phân xưởng nặn (Binary Search on Split Time)
2 O((N + M) log T * log T) O(N + M) Tối ưu hóa số lượng sản phẩm (Binary Search on Answer)
3 Rất lớn, phụ thuộc vào T và a_i O(N + M) Đơn giản hóa tham số

Bài học kinh nghiệm

  • Bài toán có thể quy về tìm giá trị tối ưu của một biến đơn (thời gian hoặc số lượng sản phẩm) bằng Binary Search.
  • Cách tiếp cận tối ưu nhất là Binary Search trên thời gian phân xưởng nặn hoạt động (vì hàm số đơn điệu và độ phức tạp tốt nhất O((N+M) log T)).
  • Phải dùng long long cho tất cả các biến liên quan đến thời gian và số lượng sản phẩm.

Lỗi thường gặp

  • Sử dụng sai kiểu dữ liệu (int thay cho long long) dẫn đến tràn số.
  • Logic nhầm lẫn giữa việc tăng/giảm thời gian cho phân xưởng nặn trong quá trình Binary Search.
  • Quên tối ưu hóa vòng lặp khi tính tổng số sản phẩm (break early khi đã đủ số lượng cần thiết).

Bình luận

Please read the guidelines before commenting.


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