Hướng dẫn giải của Mật khẩu (đề Lào Cai 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 xâu S (|S| <= 10^6) chỉ gồm chữ hoa, chữ thường và chữ số. Đếm số cặp (i, j) sao cho xâu con S[i..j] có độ dài ít nhất 6 và chứa đủ ba loại: chữ hoa, chữ thường, chữ số. Kết quả có thể lên tới khoảng 5·10^11 nên dùng long long.

Sub 1: Vét cạn — ~O(n^2)~, dùng khi ~|S| \le 3000~

Với mỗi điểm đầu i, mở rộng điểm cuối j và ghi nhận đã gặp chữ hoa, chữ thường, chữ số chưa. Nếu độ dài j - i + 1 >= 6 và đủ ba loại thì tăng đáp án.

Chấm riêng sub này: đúng 20/30 test (các test có |S| <= 3000 đều đúng).

Sub 2: Đếm theo điểm cuối — ~O(n)~, dùng cho mọi n

Với mỗi điểm cuối r, lấy lu, ll, ld là vị trí xuất hiện gần nhất (không vượt quá r) của chữ hoa, chữ thường và chữ số.

  • Đoạn [l..r] có đủ ba loại khi và chỉ khi l <= min(lu, ll, ld).
  • Đoạn có độ dài >= 6 khi và chỉ khi l <= r - 5.
  • Vậy số đoạn an toàn kết thúc tại r là min(min(lu, ll, ld), r - 5) + 1, nếu giá trị này không âm. Nếu chưa gặp đủ ba loại thì không có đoạn nào.

Ví dụ: S = "a1B2C3" có độ dài 6, chỉ có đúng một đoạn an toàn là chính S, nên đáp án là 1. Với S = "tinHoc9" (độ dài 7), tại r = 6 (ký tự '9') ta có lu = 3, ll = 5, ld = 6, min = 3 và r - 5 = 1, nên có min(3, 1) + 1 = 2 đoạn: "tinHoc9" và "inHoc9".

Bẫy: |S| có thể tới 10^6 và đáp án lớn, dùng long long. Cần kiểm tra cả ba loại đã xuất hiện trước khi cộng.

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

main đọc xâu S, nếu |S| <= 3000 gọi sub1, còn lại gọi sub2.

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

string s;
int n;

// Đoạn [i..j] (chỉ số từ 0) là "an toàn" khi độ dài >= 6 và có đủ: chữ hoa, chữ thường, chữ số.

// Sub 1: vét cạn — với mỗi điểm đầu i, mở rộng j và ghi nhận đã có đủ 3 loại ký tự chưa. O(n^2). Dùng khi |S| <= 3000.
void sub1() {
    long long ans = 0;
    for (int i = 0; i < n; i++) {
        bool up = false, low = false, dig = false;
        for (int j = i; j < n; j++) {
            char c = s[j];
            if (c >= 'A' && c <= 'Z') up = true;
            else if (c >= 'a' && c <= 'z') low = true;
            else if (c >= '0' && c <= '9') dig = true;
            if (j - i + 1 >= 6 && up && low && dig) ans++;
        }
    }
    cout << ans << "\n";
}

// Sub 2: đếm theo điểm cuối r. Gọi lu, ll, ld là vị trí xuất hiện gần nhất (<= r) của chữ hoa, chữ thường, chữ số.
// Đoạn [l..r] có đủ 3 loại khi l <= min(lu, ll, ld); độ dài >= 6 khi l <= r - 5.
// Với mỗi r, số đoạn an toàn kết thúc tại r là min(min(lu,ll,ld), r-5) + 1 (nếu thiếu loại nào thì không có đoạn nào). O(n).
void sub2() {
    long long ans = 0;
    long long lu = -1, ll = -1, ld = -1;
    for (int r = 0; r < n; r++) {
        char c = s[r];
        if (c >= 'A' && c <= 'Z') lu = r;
        else if (c >= 'a' && c <= 'z') ll = r;
        else if (c >= '0' && c <= '9') ld = r;
        if (lu < 0 || ll < 0 || ld < 0) continue;  // chưa đủ 3 loại ký tự
        long long lim = min(min(lu, ll), min(ld, (long long)r - 5));
        if (lim >= 0) ans += lim + 1;
    }
    cout << ans << "\n";
}

int main() {
    freopen("cau4.inp", "r", stdin);
    freopen("cau4.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> s;
    n = s.size();
    if (n <= 3000) sub1();  // dữ liệu nhỏ: vét cạn
    else sub2();            // đếm theo điểm cuối
    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.