Hướng dẫn giải của [KS cụm PT] Câu 2: Đếm cặp số


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

Đếm cặp (i, j), i < j, mà cả Ai và Aj đều là số nguyên tố và |Ai - Aj| <= K. Cần sàng số nguyên tố đến 10^7.

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

Thử mọi cặp (i, j) với i < j, kiểm tra cả hai là số nguyên tố và hiệu không quá K. Nếu chỉ nộp sub này: đúng 2/20 test.

Sub 2: Mảng cộng dồn theo giá trị — ~O(n + 10^7)~

Thứ tự chỉ số không ảnh hưởng đến số cặp, nên chỉ cần quan tâm giá trị. Gọi ~pre[x]~ là số phần tử nguyên tố của dãy có giá trị ~\le x~ (tính bằng mảng đếm rồi cộng dồn). Với mỗi phần tử nguyên tố ~v~, số phần tử có giá trị trong đoạn ~[v-K, v+K]~ là ~pre[v+K] - pre[v-K-1]~; trừ đi 1 vì không tính chính nó. Mỗi cặp bị đếm hai lần nên lấy kết quả chia đôi. Cách này nhanh hơn sắp xếp, vì dãy có thể tới vài triệu số. Dữ liệu lớn nên cần đọc nhanh (đọc từng khối bằng fread thay vì cin), và kết quả có thể lên tới khoảng ~5 \cdot 10^{11}~ nên dùng long long. Sub 2 đúng 20/20 test, mỗi test chạy dưới 1 giây.

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

Hàm main sàng số nguyên tố, đọc dữ liệu, gọi sub 1 khi n \le 1000, còn lại gọi sub 2.

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

int n;
long long K;
const int MAXV = 10000000;
bool composite[MAXV + 1];   // sàng Eratosthenes: composite[x] = true nếu x không phải số nguyên tố
int pre[MAXV + 1];          // pre[x] = số phần tử nguyên tố của dãy có giá trị <= x (dùng ở sub 2)
vector<int> a;              // dãy A_1..A_n

// Đọc số nhanh: dữ liệu có thể lên tới hàng triệu số nên không dùng cin
static char buf[1 << 16];
size_t buflen = 0, bufpos = 0;
int readChar() {
    if (bufpos == buflen) {
        buflen = fread(buf, 1, sizeof(buf), stdin);
        bufpos = 0;
        if (buflen == 0) return -1;
    }
    return buf[bufpos++];
}
long long readNum() {
    int c = readChar();
    while (c != -1 && (c < '0' || c > '9')) c = readChar();
    long long x = 0;
    while (c >= '0' && c <= '9') {
        x = x * 10 + (c - '0');
        c = readChar();
    }
    return x;
}

// Sub 1: vét cạn O(n^2) — thử mọi cặp (i, j), i < j. Dùng khi n <= 1000.
void sub1() {
    long long cnt = 0;
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            if (!composite[a[i]] && !composite[a[j]] && llabs((long long)a[i] - a[j]) <= K) cnt++;
    cout << cnt << "\n";
}

// Sub 2: O(n + MAXV). Thứ tự chỉ số không ảnh hưởng đến số cặp, nên chỉ cần quan tâm giá trị.
// pre[x] = số phần tử nguyên tố <= x. Với mỗi phần tử nguyên tố v, số phần tử có giá trị trong [v-K, v+K]
// là pre[v+K] - pre[v-K-1]; trừ đi 1 (chính nó). Mỗi cặp được đếm hai lần nên chia đôi.
void sub2() {
    for (int i = 0; i < n; i++)
        if (!composite[a[i]]) pre[a[i]]++;
    for (int x = 1; x <= MAXV; x++) pre[x] += pre[x - 1];
    long long tong = 0;
    for (int i = 0; i < n; i++) {
        if (composite[a[i]]) continue;
        long long lo = max(0LL, (long long)a[i] - K - 1);
        long long hi = min((long long)MAXV, (long long)a[i] + K);
        tong += pre[hi] - pre[lo] - 1;
    }
    cout << tong / 2 << "\n";
}

int main() {
    freopen("CAU2.inp", "r", stdin);
    freopen("CAU2.out", "w", stdout);

    // Sàng số nguyên tố đến 10^7
    composite[0] = composite[1] = true;
    for (long long i = 2; i * i <= MAXV; i++)
        if (!composite[i])
            for (long long j = i * i; j <= MAXV; j += i) composite[j] = true;

    n = (int)readNum();
    K = readNum();
    a.resize(n);
    for (int i = 0; i < n; i++) a[i] = (int)readNum();
    if (n <= 1000) 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.