Hướng dẫn giải của Thách Thức Lập Trình Xuân Giáp Thìn - Thử thách Vũ Môn


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.

Lời giải được cung cấp bởi bạn lftroq

Subtask 1:

Ta chỉ cần thử từng cặp ~(x, y)~ và kiểm tra xem có thỏa đề bài hay không.

Subtask 2:

Đặt:

  • ~d = gcd(x, y)~

  • ~x = u \cdot d~

  • ~y = v \cdot d~

Tất nhiên ~gcd(u, v) = 1~.

Ta có: $$x + y + gcd(x, y) = a$$ $$\Leftrightarrow d \cdot (u + v + 1) = a$$ $$\Leftrightarrow u + v = \frac{a}{d} - 1$$

Như vậy ta chỉ cần chuẩn bị sẵn mảng ~cnt~ với ~cnt[k]~ là số cặp ~(u, v)~ thỏa:

  • ~gcd(u, v) = 1~

  • ~u + v = \frac{a}{d} - 1 = k~

Ta sẽ dùng nguyên lí bù trừ để giải quyết bài toán trên.

Giả sử ~k~ có ~t~ ước nguyên tố.

Gọi ~e_i~ là số nguyên tố thứ ~i~ (~1 \leq i \leq t~) mà ~k~ chia hết. ~S_i~ là biến cố ~gcd(u, x)~ chia hết cho ~e_i~. Tức ~|S_i|~ là số cặp ~(u, v)~ sao cho:

  • ~gcd(u, v)~ ~\small{\vdots}~ ~e_i~

  • ~u + v = k~

Vậy đáp án là:

$$|U| - \sum_{i = 1}^{t}|S_i| + \sum_{1 \leq i < j \leq t}|S_i \cap S_j| - ...$$

Ta có thể tính:

  • ~|U| = k - 1~

  • ~|\bigcap\limits_{1 \leq i \leq t} S_i| = \frac{k}{\prod_{i = 1}^{t} e_i} - 1~

Thuật toán này có thể dùng Harmonic series nên có thể chạy trong độ phức tạp ~O(a\log(a))~.

Như vậy ứng với mỗi ~d~ là ước của ~a~ (có thể tìm trong ~O(\sqrt{a})~) ta chỉ cần tổng cặp số thỏa điều kiện là ra được đáp án cuối cùng.

Subtask 3:

Cũng chuẩn bị sẵn mảng ~cnt~ như subtask 2. Tuy nhiên nếu như tìm các ước của ~a_i~ trong ~O(\sqrt{a})~ ta sẽ bị TLE vì thế ta có thể cải thiện phương pháp tìm ước của ~a_i~ trong thời gian tối ưu nhất.

Giả sử số nguyên dương ~m~ có tập ước là ~div[m]~.

Khi ta xét số nguyên dương ~p = e^c~ và ~m~ ~\not\vdots~ ~e~. Như vậy chỉ cần lấy một số trong ~div[m]~ nhân cho một số trong tập ~\{e^0, e^1, ..., e^c\}~ ta sẽ được một phần tử trong tập ~div[p]~.

Như vậy ta chỉ cần quy nạp với tập ban đầu ~div[1] = {1}~.

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

const int Nmax = 4e6 + 7;

int mu[Nmax];
int cnt[Nmax];
int lp[Nmax];
vector<int> pr;

void sieve() {
    for(int i=2;i<Nmax;i++) {
        if(!lp[i]) pr.push_back(lp[i]=i);
        for(int j=0;j<pr.size()&&pr[j]<=lp[i]&&i*pr[j]<Nmax;j++) {
            lp[i*pr[j]] = pr[j];
        }
    }
}

vector<int> factorize(int n) {
    vector<int> div{1};
    int tn = n;
    for(int p=lp[tn];tn!=1;p=lp[tn]) {
        int cnt = 0;
        while(tn % p == 0) tn/= p, cnt++;


        vector<int> nw;
        for(int j=p, i=1;i<=cnt;i++, j*= p) {
            for(int d : div) nw.push_back(d * j);
        }
        div.insert(div.end(), nw.begin(), nw.end());
    }
    return div;
}

void evadi(int Q, vector<int>& N) {
    sieve();
    mu[1] = 1;
    for (int i = 1; i <= Nmax; i++)
      for (int j = 2*i; j <= Nmax; j += i)
          mu[j] -= mu[i];

    for(int i=Nmax-1;i>0;i--) {
        cnt[i] = i-1;
        for(int j=i*2;j<Nmax;j+=i) {
            cnt[j]+= cnt[i] * mu[j/i];
        }
    }

    for(int i=0;i<Q;i++) {
        int n = N[i];
        vector<int> div = factorize(n);
        long long ans = 0;

        for(int d : div) {
            ans+= cnt[n / d - 1];
        }
        N[i] = ans;
    }
}

int main() {
    cin.exceptions(istream::failbit);
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int Q;
    cin >> Q;
    vector<int> N(Q);
    for (int& i : N) {
        cin >> i;
    }

    evadi(Q, N);

    for (int i = 0; i < Q; i++) {
      cout << N[i] << " ";
    }
    cout << endl;

    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.



  • 7
    vrski39  đã bình luận lúc 25, Tháng 2, 2024, 14:38

    Có 1 cách tính khác cho cnt: ở bước u+v = k

    Dễ thấy gcd(u, v) = gcd(u, u+v) = gcd(u, k) = 1

    --> gcd(u,v)=1, gcd(u,k)=1 và gcd(v,k)=1 thoả mãn đồng thời

    --> Số nghiệm thoả mãn chính là phi(k), trừ trường hợp phi(1), vì lúc đấy có nghiệm (1, 0) và ta không lấy v=0