Hướng dẫn cho Chia tiền thưởng


Chỉ sử dụng khi thực sự cần thiết như một cách tôn trọng tác giả và người viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là hành vi có thể dẫn đến khóa tài khoản.

Authors: Flower_On_Stone

Subtask 1

Sử dụng if-else để liệt kê các khả năng có thể xảy ra.

  • Cả An và Bình không lấy tờ tiền nào.
  • An lấy tờ tiền thứ nhất, Bình lấy tờ tiền thứ hai.
  • An lấy tờ tiền thứ nhất, Bình lấy tờ tiền thứ hai và ba.
  • An lấy tờ tiền thứ hai, Bình lấy tờ tiền thứ nhất.
    ...

Kiểm tra tất cả các trường hợp và với mỗi trường hợp, kiểm tra xem số tiền An và Bình nắm giữ có bằng nhau không.

Subtask 2

Với mỗi tờ tiền, có \(3\) khả năng có thể xảy ra: An giữ, Bình giữ hoặc được đem đi đầu tư. Ta có thể duyệt qua \(3^n\) khả năng để tìm ra phương án tối ưu nhất sử dụng quay lui.

Code tham khảo C++
C++
int n, a[15];
int res = 0;

void ql(int i, int An, int Binh){
    if (i == n + 1){
        if (An == Binh) res = max(res, An);
        return;
    }

    ql(i + 1, An + a[i], Binh); // An giữ tờ tiến thứ i
    ql(i + 1, An, Binh + a[i]); // Bình giữ tờ tiền thứ i
    ql(i + 1, An, Binh); // tờ tiền được đem đi đầu tư
}

Subtask 3

Subtask này ta sẽ sử dụng quy hoạch động.

Gọi \(f[i][diff]\) là lượng tiền đem đi đầu tư ít nhất khi xét đến tờ thứ \(i\) và lượng tiền chênh lệch giữa An và Bình là \(diff\).

Tương tự, vẫn có \(3\) trường hợp có thể xảy ra với tờ tiền thứ \(i\):

  • An giữ: \(f[i][diff] = f[i - 1][diff - a[i]]\)
  • Bình giữ: \(f[i][diff] = f[i - 1][diff + a[i]]\).
  • Đem đi đầu tư: \(f[i][diff] = f[i - 1][diff] + a[i]\).

Giá trị của \(f[i][diff]\) là min của 3 trường hợp trên.

Gọi \(sum\) là tổng giá trị của \(n\) tờ tiền. Đáp án bài toán sẽ là \(\frac{sum - f[n][0]}{2}\)

Lưu ý:

  • Giá trị của \(diff\) có thể âm \((-10^5 \le diff \le 10^5)\) nên ta cần cộng thêm biến \(diff\) một lượng \(base\)\(10^5\) để tránh truy cập vào mảng có index âm.
  • Việc lưu mảng \(f[i][diff]\) cần dùng đến \(500 \times 10^5 \times 2 = 10^8\) int nên sẽ gây tràn bộ nhớ (MLE). Để ý rằng giá trị của \(f[i]\) chỉ dựa vào các \(f[i - 1]\) nên ta chỉ cần duy tri trạng thái của vị trí hiện tại \(i\) và vị trí trước đấy \(i-1\) và cập nhật lần lượt sau khi xét đến vị trí \(i + 1\).
Code tham khảo C++
C++
#include <bits/stdc++.h>
#define ii pair<int, int>
#define st first
#define nd second
#define endl "\n"
#define all(v) v.begin(), v.end()
#define Unique(v) v.erase(unique(all(v)), v.end())

using namespace std;
const int oo = 1e9;

const int base = 1e5;
const int maxw = 1e5 + 5;
const int maxn = 505;

int n, a[maxn];
int dp[2][maxw + base];

void PROGRAM(int _){
    cin >> n;

    for (int i = 1; i <= n; i++) cin >> a[i];

    int sum = accumulate(a + 1, a + n + 1, 0ll);


    for (int weight = 0; weight <= base * 2; weight++){
        dp[0][weight] = oo;
        dp[1][weight] = oo;
    }

    dp[0][base] = 0;

    for (int i = 1; i <= n; i++){
        for (int w = 0; w <= base * 2; w++){
            if (w + a[i] <= base * 2) dp[1][w] = min(dp[1][w], dp[0][w + a[i]]);
            if (w - a[i] >= 0) dp[1][w] = min(dp[1][w], dp[0][w - a[i]]);
            dp[1][w] = min(dp[1][w], dp[0][w] + a[i]);
        }
        for (int w = 0; w <= base * 2; w++){
            dp[0][w] = dp[1][w];
            dp[1][w] = oo;
        }
    }

    cout << (sum - dp[0][base]) / 2;

}

signed main(){
    freopen("CT.INP", "r", stdin);
    freopen("CT.OUT", "w", stdout);
    ios_base::sync_with_stdio(false);
    cin.tie(0);

    int test = 1;

    for (int _ = 1; _ <= test; _++){
        PROGRAM(_);
    }

    return 0;
}


Bình luận

Không có bình luận nào.