Hướng dẫn giải của [KS cụm QL] Câu 3: Số nam nữ


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

Số "nam nữ" là số mà hai chữ số liên tiếp luôn khác tính chẵn lẻ. Cần đếm số nguyên dương không lớn hơn N có tính chất này.

Sub 1: Vét cạn — ~O(N \cdot 10)~, dùng khi ~N \le 10^6~

Duyệt từng số từ 1 đến N, kiểm tra các chữ số liên tiếp có xen kẽ chẵn lẻ không. Nếu chỉ nộp sub này: đúng 15/20 test.

Sub 2: Đếm theo chữ số — ~O(\text{số chữ số}^2)~

Gọi h[k][q] là số cách điền k chữ số tiếp theo khi chữ số trước có tính chẵn lẻ q. Chữ số tiếp theo phải khác tính chẵn lẻ q, và có 5 lựa chọn cho mỗi loại. Các số ít chữ số hơn N được cộng trực tiếp. Với các số cùng độ dài N, duyệt từng vị trí, cộng số cách với các chữ số nhỏ hơn chữ số của N, rồi kiểm tra chính N. Sub 2 đúng 20/20 test.

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

Hàm main đọc N. Nếu N \le 10^6 gọi sub 1, còn lại gọi sub 2.

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

long long N;

// Số "nam nữ": các chữ số liên tiếp luôn khác tính chẵn/lẻ (chẵn-lẻ xen kẽ).

// Kiểm tra một số có phải số "nam nữ" không
bool isNamNu(long long x) {
    int prev = -1;   // tính chẵn/lẻ của chữ số trước (-1 = chưa có)
    while (x > 0) {
        int d = x % 10;
        int par = d % 2;
        if (prev != -1 && par == prev) return false;
        prev = par;
        x /= 10;
    }
    return true;
}

// Sub 1: vét cạn O(N * 10). Dùng khi N <= 10^6.
void sub1() {
    long long cnt = 0;
    for (long long x = 1; x <= N; x++)
        if (isNamNu(x)) cnt++;
    cout << cnt << "\n";
}

// Sub 2: đếm theo chữ số (digit DP), O(số chữ số^2).
// h[k][q] = số cách điền k chữ số tiếp theo, biết chữ số trước có tính chẵn/lẻ q.
// Chữ số tiếp theo phải khác tính chẵn/lẻ q; có cnt[1-q] lựa chọn, rồi trạng thái là 1-q.
long long h[20][2];
long long cntPar[2] = {5, 5};          // số chữ số 0..9 chẵn (0) và lẻ (1)
long long cntFirst[2] = {4, 5};        // chữ số đầu khác 0: chẵn {2,4,6,8} = 4, lẻ {1,3,5,7,9} = 5

void sub2() {
    for (int q = 0; q < 2; q++) h[0][q] = 1;
    for (int k = 1; k < 20; k++)
        for (int q = 0; q < 2; q++) h[k][q] = cntPar[1 - q] * h[k - 1][1 - q];

    string s = to_string(N);
    int L = s.size();
    long long ans = 0;
    // các số có ít chữ số hơn N
    for (int len = 1; len < L; len++)
        for (int p = 0; p < 2; p++) ans += cntFirst[p] * h[len - 1][p];

    // các số có đúng L chữ số và <= N
    int prev = -1;
    bool valid = true;
    for (int i = 0; i < L && valid; i++) {
        int c = s[i] - '0';
        // chọn chữ số d < c tại vị trí i (d >= 1 nếu là vị trí đầu)
        for (int d = (i == 0 ? 1 : 0); d < c; d++) {
            int par = d % 2;
            if (prev != -1 && par == prev) continue;
            ans += h[L - i - 1][par];
        }
        // giữ nguyên chữ số c, kiểm tra xem còn đi tiếp được không
        int par = c % 2;
        if (prev != -1 && par == prev) valid = false;
        prev = par;
    }
    if (valid) ans += 1;   // chính N là số nam nữ
    cout << ans << "\n";
}

int main() {
    freopen("CAU3.INP", "r", stdin);
    freopen("CAU3.OUT", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> N;
    if (N <= 1000000) sub1();
    else sub2();
    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.