Hướng dẫn giải của Xuất hiện nhiều hơn K lần (đề 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 dãy N số. Đếm số đoạn liên tiếp [i..j] mà trong đó có ít nhất một giá trị xuất hiện từ K lần trở lên. Số đoạn có thể lên tới khoảng N^2/2 nên cần dùng long long.

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

Với mỗi điểm đầu i, mở rộng điểm cuối j từ i đến N, cập nhật số lần xuất hiện của a_j và giữ giá trị lớn nhất của số lần. Nếu giá trị lớn nhất này >= K thì đoạn [i..j] được tính. Các giá trị được nén về 0..m-1 để dùng mảng đếm.

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

Sub 2: Đếm phần bù bằng hai con trỏ — ~O(n \log n)~, dùng cho mọi n

Đếm các đoạn "xấu" (mọi giá trị đều xuất hiện ít hơn K lần) rồi lấy tổng số đoạn trừ đi.

  • Với mỗi đầu phải r, giữ con trỏ l sao cho đoạn [l..r] là đoạn xấu dài nhất kết thúc tại r. Khi thêm ar mà số lần của ar đạt K, tăng l và bỏ dần phần đầu cho đến khi a_r còn dưới K lần.
  • Mọi đoạn [s..r] với s >= l đều xấu, nên có r - l + 1 đoạn xấu kết thúc tại r.
  • Đáp án là n(n+1)/2 - tổng số đoạn xấu.

Ví dụ: N = 4, K = 2, dãy 1 2 1 2. Tổng số đoạn là 10. Đoạn xấu gồm 4 đoạn độ dài 1 và 3 đoạn độ dài 2 ({1,2}, {2,1}, {1,2}), tổng 7. Các đoạn độ dài 3 trở lên đều có một giá trị xuất hiện 2 lần nên không xấu. Đáp án là 10 - 7 = 3, đúng với đề.

Bẫy: a_i có thể lên tới 10^9 nên cần nén giá trị; n lên tới 5·10^5 nên dùng long long cho tổng.

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

main đọc N, K và dãy a, nén giá trị, nếu n <= 1000 gọi sub1, còn lại gọi sub2.

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

int n, K;
vector<int> a;  // a[0..n-1] da nen thanh chi so 0..m-1
int m;          // so gia tri khac nhau

// Đếm đoạn [i..j] có ít nhất một giá trị xuất hiện >= K lần.

// Sub 1: vét cạn — với mỗi điểm đầu i, mở rộng j và cập nhật số lần xuất hiện, kiểm tra max >= K. O(n^2). Dùng khi n <= 1000.
void sub1() {
    vector<int> cnt(m, 0);
    long long ans = 0;
    for (int i = 0; i < n; i++) {
        fill(cnt.begin(), cnt.end(), 0);
        int mx = 0;
        for (int j = i; j < n; j++) {
            mx = max(mx, ++cnt[a[j]]);
            if (mx >= K) ans++;
        }
    }
    cout << ans << "\n";
}

// Sub 2: đếm phần bù rồi trừ. Đoạn "xấu" là đoạn mà mọi giá trị đều xuất hiện < K lần.
// Với mỗi đầu phải r, giữ cửa sổ [l..r] nhỏ nhất sao cho mọi giá trị trong đó xuất hiện < K lần (hai con trỏ).
// Khi đó có r - l + 1 đoạn xấu kết thúc tại r. Đáp án = n(n+1)/2 - tổng đoạn xấu. O(n log n).
void sub2() {
    vector<int> cnt(m, 0);
    long long bad = 0;
    int l = 0;
    for (int r = 0; r < n; r++) {
        cnt[a[r]]++;
        while (cnt[a[r]] >= K) {  // bỏ dần phần đầu cho đến khi giá trị a[r] còn < K lần
            cnt[a[l]]--;
            l++;
        }
        bad += r - l + 1;
    }
    long long total = (long long)n * (n + 1) / 2;
    cout << total - bad << "\n";
}

int main() {
    freopen("cau3.inp", "r", stdin);
    freopen("cau3.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> K;
    vector<long long> raw(n);
    for (auto &x : raw) cin >> x;
    // nén giá trị về 0..m-1
    vector<long long> vals = raw;
    sort(vals.begin(), vals.end());
    vals.erase(unique(vals.begin(), vals.end()), vals.end());
    m = vals.size();
    a.resize(n);
    for (int i = 0; i < n; i++)
        a[i] = lower_bound(vals.begin(), vals.end(), raw[i]) - vals.begin();
    if (n <= 1000) sub1();  // dữ liệu nhỏ: vét cạn
    else sub2();            // hai con trỏ
    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.