Hướng dẫn giải của VPTIN10 18 19 Đếm [COUNT]


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, minhminh123, sussyboy, tranhaiancpp

Hiểu bài toán

Bài toán yêu cầu tính số cách để tạo ra một dãy số có độ dài $N$ sao cho tổng các phần tử trong dãy chia hết cho 3. Các phần tử trong dãy có thể là 1 hoặc 2 (hoặc các số tự nhiên khác tùy vào ngữ cảnh bài gốc, nhưng dựa trên các giải pháp được cung cấp, có vẻ như đây là bài toán đếm số cách tạo tổng chia hết cho 3 với các số hạng cơ bản là 3, hoặc đơn giản là $3^{N-1}$). Tuy nhiên, dựa trên output là $3^{N-1}$, bài toán này có thể là một bài toán quy hoạch động/tổ hợp đơn giản về cách chọn các số để tổng chia hết cho 3, hoặc một bài toán có tính chất số học đơn giản. Với input là $N$ và output là $3^{N-1}$ modulo $10^9+7$, bài toán có thể là đếm số cách chọn các số để tổng chia hết cho 3 với $N$ số hạng, hoặc một biến thể tương tự. Giả sử bài toán là đếm số cách điền $N$ vị trí với các số như 1, 2, 3 sao cho tổng chia hết cho 3. Nếu các số là 1, 2, 3 thì có $3^N$ cách điền, trong đó 1/3 tổng số cách có tổng chia hết cho 3, tức là $3^N / 3 = 3^{N-1}$.

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

Cách Quy hoạch động cơ bản (Dựa theo mô tả)
int dp[N+1][3];
dp[0][0] = 1;
for(int i=1; i<=N; i++) {
    for(int j=0; j<3; j++) {
        dp[i][j] = (dp[i-1][(j-1+3)%3] + dp[i-1][(j-2+3)%3] + dp[i-1][(j-3+3)%3]) % MOD;
    }
}
cout << dp[N][0];
  • Time Complexity: O(N)
  • Space Complexity: O(N)

Sử dụng mảng 2 chiều dp[i][j] để lưu số cách tạo tổng có dư là j khi dùng i số. Tuy nhiên, các giải pháp accepted lại sử dụng công thức $3^{N-1}$, cho thấy có thể có một công thức bí mật hoặc cách tiếp cận tối ưu hơn. Cách này thường quá chậm cho $N$ lớn (ví dụ $10^{18}$) và chỉ mang tính chất tham khảo cho logic cơ bản.

Cách Tối ưu quy hoạch động (Xử lý mảng 1 chiều)
int dp[3] = {1, 0, 0};
for(int i=1; i<=N; i++) {
    int new_dp[3];
    new_dp[0] = (dp[0] + dp[2] + dp[1]) % MOD; // Neu cac so co the la 1, 2, 3
    new_dp[1] = (dp[0] + dp[2] + dp[1]) % MOD; // ... logic phu hop
    new_dp[2] = ...;
    // Cap nhat dp
}
cout << dp[0];
  • Time Complexity: O(N)
  • Space Complexity: O(1)

Thay vì lưu toàn bộ bảng, ta chỉ cần 3 biến lưu số cách cho các tổng dư 0, 1, 2. Nâng cấp từ DP cơ bản. Tuy nhiên, vẫn chưa đạt được độ phức tạp logarithmic cần thiết cho $N$ lên tới $10^{18}$.

Cách Công thức số học (Ma trận lũy thừa)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int nhan(int a, int b) {
    if (b == 0) return 0;
    int t = nhan(a, b / 2);
    t = (t + t) % 1000000007;
    if (b % 2 == 1) t = (t + a) % 1000000007;
    return t;
}
int power(int a, int b) {
    if (b == 0) return 1;
    int t = power(a, b / 2);
    t = (t * t) % 1000000007;
    if (b % 2 == 1) t = (t * a) % 1000000007;
    return t;
}
signed main() {
    //freopen...
    cin >> n;
    cout << power(3, n-1);
}
  • Time Complexity: O(log N)
  • Space Complexity: O(1)

Bài toán có tính chất lặp lại đều đặn. Nếu các số được chọn từ tập {1, 2, 3}, có $3^N$ cách điền. Tính chia đều cho các tổng dư 0, 1, 2 modulo 3, ta có $3^N / 3 = 3^{N-1}$ cách để tổng chia hết cho 3. Sử dụng thuật toán lũy thừa nhanh (Binary Exponentiation) để tính $3^{N-1}$ trong thời gian logarithmic.

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

Cách tiếp cận Time Space Tên
1 O(N) O(N) Quy hoạch động cơ bản (Dựa theo mô tả)
2 O(N) O(1) Tối ưu quy hoạch động (Xử lý mảng 1 chiều)
3 O(log N) O(1) Công thức số học (Ma trận lũy thừa)

Bài học kinh nghiệm

  • Bài toán có tính chất đối xứng: Nếu các số được chọn từ một tập hợp có tổng chia hết cho 3 (ví dụ 1, 2, 3 có tổng 6), thì số cách tạo tổng chia hết cho 3 bằng 1/3 tổng số cách tạo dãy.
  • Đối với $N$ lớn, các thuật toán lặp (DP $O(N)$) sẽ quá chậm, cần sử dụng lũy thừa nhanh (Binary Exponentiation) hoặc công thức trực tiếp.
  • Thao tác nhân số lớn trong C++ cần cẩn thận tràn số, có thể dùng 'long long' và chia đôi hoặc dùng hàm nhân an toàn.

Lỗi thường gặp

  • Tràn số khi tính toán: $N$ có thể lên tới $10^{18}$, nên lũy thừa cần thực hiện modulo liên tục.
  • Quên xử lý trường hợp $N=0$ hoặc $N=1$ (nếu công thức không xử lý đúng).
  • Sai logic quy hoạch động nếu các số cho phép không tạo thành một nhóm đối xứng hoàn hảo modulo 3.

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.