Hướng dẫn giải của Cổ phiếu VNI (đề Hà Nội 2021)


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 giá cổ phiếu ~A_1, \dots, A_N~. Mỗi ngày Bình chỉ được mua 1 cổ phiếu, hoặc bán số lượng bất kỳ đang có, hoặc không làm gì. Với mỗi truy vấn ~T~ (bắt đầu giao dịch từ ngày ~T~) cần tìm lợi nhuận lớn nhất.

Sub 1: Quy hoạch động — ~O(N^2 \cdot Q)~, dùng khi ~N \le 1000~, ~Q \le 10~

Gọi ~f[h]~ là lợi nhuận lớn nhất khi đang giữ đúng ~h~ cổ phiếu. Mỗi ngày có thể mua 1 (~h \to h+1~, trừ giá), bán xuống còn ~h'~ cổ phiếu (cộng ~(h-h') \cdot A_j~) hoặc không làm gì. Đáp án là lợi nhuận lớn nhất trong các ~f[h]~ cuối cùng (bán hết hoặc để lại cổ phiếu không có lợi). Nếu chỉ nộp sub này: đúng 11/20 test, còn lại quá thời gian.

Sub 2: Tham lam bằng hàng đợi ưu tiên — ~O(N \log N \cdot Q)~, dùng khi ~Q \le 10~

Với mỗi truy vấn duyệt ngày ~T, T+1, \dots, N~. Đưa ~A_j~ vào hàng đợi nhỏ nhất; nếu số nhỏ nhất trong hàng đợi nhỏ hơn ~A_j~ thì "bán" với lãi ~A_j~ trừ số đó, bỏ số đó ra và đẩy lại ~A_j~ để sau này có thể đổi ý. Nếu chỉ nộp sub này: đúng 11/20 test (quá thời gian khi có nhiều truy vấn).

Sub 3: Hậu tố — ~O(N + Q)~

Mỗi cổ phiếu mua ở ngày ~j~ nên được bán ở ngày có giá cao nhất từ ~j~ trở về sau. Vì được mua 1 cổ phiếu mỗi ngày và bán bao nhiêu tùy ý, ngày ~j~ đóng góp lãi ~mx_j - A_j~ với ~mx_j = \max(A_j, \dots, A_N)~ (lãi này không âm và chỉ phụ thuộc phần sau ngày ~j~, không phụ thuộc ~T~). Vậy đáp án truy vấn ~T~ là ~\sum_{j=T}^{N} (mx_j - A_j)~: tính tổng hậu tố một lần, mỗi truy vấn trả lời ngay. Tổng có thể tới khoảng ~10^{14}~ nên dùng long long. Ví dụ ~A = 1, 2, 5, 4~: ~mx = 5, 5, 5, 4~, các lãi là ~4, 3, 0, 0~, truy vấn ~T = 1~ cho 7, ~T = 3~ cho 0.

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

main đọc dữ liệu và các truy vấn; nếu ~N \le 1000~ và ~Q \le 10~ gọi sub1, nếu ~Q \le 10~ gọi sub2, còn lại gọi sub3. Khi đi thi nên viết như vậy: sub sau có sai hoặc chưa kịp làm thì vẫn ăn điểm sub trước. Kết quả chấm: đúng 20/20 test.

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

int N, Q;
long long A[100005];  // A[1..N]: giá cổ phiếu từng ngày
vector<int> qs;       // các ngày bắt đầu T của từng truy vấn
const long long NEG = LLONG_MIN / 4;

// Bình bắt đầu từ ngày T. Mỗi ngày: mua 1 cổ phiếu, hoặc bán số lượng bất kỳ đang có, hoặc không làm gì.
// Lợi nhuận = tiền bán - tiền mua.

// Sub 1: quy hoạch động theo số cổ phiếu đang giữ. f[h] = lợi nhuận lớn nhất khi đang giữ đúng h cổ phiếu.
// Mỗi ngày: mua (h -> h+1) hoặc bán k cổ phiếu (h -> h-k). O(N^2) mỗi truy vấn. Dùng khi N <= 1000, Q <= 10.
long long solveDP(int T) {
    vector<long long> f(N + 2, NEG), g(N + 2, NEG);
    f[0] = 0;
    int maxH = 0;
    for (int j = T; j <= N; j++) {
        long long p = A[j];
        g = f;  // không làm gì
        for (int h = 0; h <= maxH; h++)  // mua 1 cổ phiếu
            if (f[h] > NEG) g[h + 1] = max(g[h + 1], f[h] - p);
        // bán xuống còn h cổ phiếu: g[h] = max theo h2 > h của f[h2] + (h2 - h) * p
        long long best = NEG;
        for (int h = maxH; h >= 0; h--) {
            if (best > NEG) g[h] = max(g[h], best - (long long)h * p);
            if (f[h] > NEG) best = max(best, f[h] + (long long)h * p);
        }
        f = g;
        maxH++;
    }
    long long ans = 0;
    for (int h = 0; h <= N + 1; h++) ans = max(ans, f[h]);
    return ans;
}

void sub1() {
    for (int T : qs) cout << solveDP(T) << "\n";
}

// Sub 2: tham lam bằng hàng đợi ưu tiên cho từng truy vấn (dùng khi Q <= 10). Mỗi ngày j thêm giá A[j] vào hàng đợi;
// nếu giá nhỏ nhất trong hàng đợi nhỏ hơn A[j] thì "bán" với lãi A[j] - giá đó rồi đẩy lại A[j]
// (để sau này có thể đổi ý bán ở ngày giá cao hơn). O(N log N) mỗi truy vấn.
long long solveHeap(int T) {
    priority_queue<long long, vector<long long>, greater<long long>> H;
    long long profit = 0;
    for (int j = T; j <= N; j++) {
        H.push(A[j]);
        while (!H.empty() && H.top() < A[j]) {
            profit += A[j] - H.top();
            H.pop();
            H.push(A[j]);
        }
    }
    return profit;
}

void sub2() {
    for (int T : qs) cout << solveHeap(T) << "\n";
}

// Sub 3: mỗi cổ phiếu mua ở ngày j nên bán ở ngày có giá cao nhất từ j về sau, nên mua 1 cổ phiếu mỗi ngày
// khi giá cao nhất về sau lớn hơn A[j]. Lãi của ngày j là mx[j] - A[j] với mx[j] = max(A[j..N]) (không phụ thuộc T).
// Đáp án truy vấn T = tổng các lãi từ ngày T đến N: tính sẵn tổng hậu tố, mỗi truy vấn O(1). Tổng O(N + Q).
void sub3() {
    vector<long long> suf(N + 2, 0);
    long long mx = 0;
    for (int j = N; j >= 1; j--) {
        mx = max(mx, A[j]);
        suf[j] = suf[j + 1] + (mx - A[j]);
    }
    for (int T : qs) cout << suf[T] << "\n";
}

int main() {
    freopen("vni.inp", "r", stdin);
    freopen("vni.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> N;
    for (int i = 1; i <= N; i++) cin >> A[i];
    cin >> Q;
    qs.resize(Q);
    for (auto &t : qs) cin >> t;
    if (N <= 1000 && Q <= 10) sub1();
    else if (Q <= 10) sub2();
    else sub3();
    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.