Hướng dẫn giải của [KSCUM VT 02] CÂU 3: Mật độ xuất hiện cao


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

Đoạn con X có mật độ cao khi tồn tại một chữ cái c mà số lần xuất hiện của c lớn hơn số ký tự còn lại, tức là 2 \cdot cnt_c > độ dài. Cần tìm độ dài lớn nhất của các đoạn như vậy.

Sub 1: Vét cạn — ~O(26 \cdot n^2)~, dùng khi ~n \le 2000~

Với mỗi điểm đầu l, mở rộng r và đếm số lần xuất hiện của từng chữ cái. Với mỗi đoạn [l, r], kiểm tra xem có chữ cái nào thỏa mãn 2 \cdot cnt > độ dài. Nếu chỉ nộp sub này: đúng 12/20 test.

Sub 2: Tiền tố — ~O(26 \cdot n)~

Với mỗi chữ cái c, đặt vi = 2 \cdot (số lần c trong i ký tự đầu) - i. Đoạn (p, r] có mật độ cao khi vr > vp. Vì v thay đổi đúng 1 mỗi bước và v0 = 0, nếu vr > 0 thì p = 0 là tốt nhất; nếu vr <= 0 thì p nhỏ nhất là lần đầu tiên v đạt giá trị v_r - 1. Lưu vị trí xuất hiện đầu tiên của mỗi giá trị v để tra cứu. Sub 2 đúng 20/20 test.

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

Hàm main đọc chuỗi S. Nếu n \le 2000 thì gọi sub 1, còn lại gọi sub 2.

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

string s;      // chuỗi S, đánh số từ 1
int n;

// Đoạn [l, r] có mật độ cao  <=>  tồn tại ký tự c mà 2 * cnt_c(l, r) > (r - l + 1)

// Sub 1: vét cạn O(26 * n^2) — thử mọi đoạn [l, r], đếm số lần xuất hiện của từng chữ cái.
// Dùng khi n <= 2000.
void sub1() {
    int best = 0;
    for (int l = 0; l < n; l++) {
        int cnt[26] = {0};
        for (int r = l; r < n; r++) {
            cnt[s[r] - 'a']++;
            int len = r - l + 1;
            for (int c = 0; c < 26; c++)
                if (2 * cnt[c] > len) {
                    best = max(best, len);
                    break;
                }
        }
    }
    cout << best << "\n";
}

// Sub 2: O(26 * n). Với mỗi ký tự c, đặt v_i = 2 * (số lần c trong i ký tự đầu) - i.
// Đoạn (p, r] có mật độ cao nếu v_r > v_p. Vì v thay đổi mỗi bước đúng ±1 và v_0 = 0:
//   - nếu v_r > 0 thì p = 0 là tốt nhất, độ dài r;
//   - nếu v_r <= 0 thì p nhỏ nhất là lần đầu tiên v đạt giá trị v_r - 1.
void sub2() {
    int best = 0;
    const int OFF = n + 1;   // dịch chỉ số âm
    vector<int> first(2 * n + 5);
    for (int c = 0; c < 26; c++) {
        fill(first.begin(), first.end(), -1);
        int v = 0;
        first[v + OFF] = 0;
        for (int r = 1; r <= n; r++) {
            v += (s[r - 1] - 'a' == c) ? 1 : -1;
            if (v > 0) {
                best = max(best, r);
            } else if (first[v - 1 + OFF] != -1) {
                best = max(best, r - first[v - 1 + OFF]);
            }
            if (first[v + OFF] == -1) first[v + OFF] = r;
        }
    }
    cout << best << "\n";
}

int main() {
    freopen("CAU3.INP", "r", stdin);
    freopen("CAU3.OUT", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> s;
    n = s.size();
    if (n <= 2000) sub1();   // dữ liệu nhỏ: vét cạn cho chắc
    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.