Hướng dẫn giải của duong3982oj Contest 03 - Số chính phương


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.

Tác giả: duong3982

Subtask 1: ~N \le 100, Q \le 10~.

Với subtask này, ta có thể duyệt mọi bộ ba ~A~, ~B~, ~C~, và tính được kết quả.

Độ phức tạp: ~O~ (~N^3 \times Q~).

Subtask 2: ~N \le 10^3, Q \le 10~.

Xuyên suốt bài toán, định nghĩa sau sẽ được sử dụng:

Gọi dạng square-free của một số là dạng mà đã bỏ đi tất cả các thừa số có số mũ chẵn.

Ví dụ:

~343 = 7^3~ ta sẽ rút gọn còn ~7~.

~20 = 2^2 \times 5~ ta sẽ rút gọn còn ~5~.

~9 = 3^2~ ta sẽ rút gọn còn ~1~.

~1029 = 7^3 \times 3~ ta sẽ rút gọn còn ~7 \times 3 = 21~.

Nhận xét: Hai số có tích là số chính phương khi và chỉ khi chúng có cùng dạng square-free.

Điều này có thể dễ dàng chứng minh bằng phản chứng. Nếu hai số không cùng dạng square-free, có nghĩa là tồn tại một thừa số có số mũ chẵn ở số này, nhưng có số mũ lẻ ở số kia. Khi nhân lại, thừa số đó sẽ có số mũ lẻ, từ đó, tích hai số không thể là số chính phương.

Ban đầu đặt ~a_i = i~ với ~1 \le i \le n~. Từ đây, ta sẽ rút gọn ~i~ thành dạng square-free. Ở subtask này, chúng ta có thể phân tích ra thừa số nguyên tố bằng thuật toán ~O~ (~\sqrt n~).

Bài toán trở thành tìm số bộ ba ~i, j, k~ mà ~a_i = a_j = a_k~. Điều này có thể thực hiện bằng mảng đếm và tổ hợp chập ~3~.

Độ phức tạp: ~O~ (~Q \times N \times \sqrt N~).

Subtask 3: ~N \le 5 \times 10^5~.

Vì ~Q~ lớn, ta sẽ nghĩ đến việc tiền xử lý các truy vấn.

Gọi ~res_i~ là kết quả khi ~N = i~.

Có thể thấy, khi chuyển từ ~i - 1~ lên ~i~, thì ta sẽ tăng ~cnt_{a_i}~ lên một, và cập nhật lại tổ hợp.

Đồng thời, ta có thể sử dụng sàng nguyên tố để phân tích thừa số nguyên tố.

Độ phức tạp: ~O~ (~N \times log~ ~n~ ~+~ ~Q~).

Subtask 4: ~N \le 10^7~.

Ta cần cải tiến bước phân tích thành thừa số nguyên tố.

Gọi ~p_i~ là ước nhỏ nhất của ~i~. Điều này có thể thực hiện trong ~O~ (~N \times~ ~log~ ~log~ ~N~).

Gọi ~square_i~ là dạng square-free của ~i~. Có ~square_1 = 1~ và ~square_2 = 2~.

Để tính ~square_i~, gọi ~x~ là một ước nguyên tố bất kỳ của ~i~. Tuy nhiên, để đơn giản, ta có thể lấy ~x = p_i~. Có thể chứng minh ~x~ sẽ là một số nguyên tố.

Lúc này, nếu ~square_{\frac {i}{x}}~ chia hết cho ~x~, điều đó có nghĩa, ~square_{\frac {i}{x}}~ đã có thừa số ~x~. Vì vậy, ~square_{i}~ ~=~ ~square_{\frac {i}{x}} / x~.

Ngược lại, ~square_i~ ~=~ ~square_{i}~ ~=~ ~square_{\frac {i}{x}} \times x~, vì ta sẽ phải thêm một thừa số ~x~.

Từ đó, ta dễ dàng tính được ~res_i~.

Độ phức tạp: ~O~ (~N \times~ ~log~ ~log~ ~N~ ~+~ ~Q~).

Code mẫu:

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

#define int long long
#define pp pair <int, int>
#define fi first
#define se second
#define yes cout << "YES\n"
#define no cout << "NO\n"

const int N = 1e7 + 9;

int n, a[N];
int p[N];
int d[N];
int res[N];
int cnt[N];

int c3 (int x){
    return x * (x - 1) * (x - 2) / 6;
}

signed main (){
    ios_base::sync_with_stdio (false);
    cin.tie (NULL);
    cout.tie (NULL);
    if (fopen ("input.txt", "r")){
        freopen ("input.txt", "r", stdin);
        freopen ("output.txt", "w", stdout);
    }
    for (int i = 1; i < N; i++) p[i] = i;
    for (int i = 2; i * i < N; i++) if (p[i] == i){
        for (int j = i * i; j < N; j += i) p[j] = min (p[j], i);
    }
    d[1] = 1;
    for (int i = 2; i < N; i++){
        if (d[i / p[i]] % p[i] == 0) d[i] = d[i / p[i]] / p[i];
        else d[i] = d[i / p[i]] * p[i];
    }
    for (int i = 1; i < N; i++){
        res[i] = res[i - 1];
        res[i] -= c3 (cnt[d[i]]);
        cnt[d[i]]++;
        res[i] += c3 (cnt[d[i]]);
    }
    int q = 1; cin >> q;
    int finalres = 0;
    while (q--){
        int x; cin >> x; finalres ^= res[x];
    }
    cout << finalres;
}

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.