CF1900D - Small GCD【数论超好题】

CF1900D - Small GCD【数论超好题】

前置知识

正题

题意

给定一个数组 $a$,找出 $a$ 中所有不同的三元组 $(x, y, z)$,求所有 $\gcd(min1, min2)$ 的和,$min1$ 为三元组中最小的数,$min2$ 为三元组中次小的数。

思路

由于该题是取三元组中较小的两个数,所以说最大的那个数并不重要,只需要找出所有二元组,并乘上可以为第三个数的数的个数即可。

再而,取三元组并不需要考虑下标,所以我们可以将 $a$ 数组升序排序,快速得到可以为第三个数的数的个数,即 $n - j$($j$ 为次小的数的排序后下标)。

显然,我们现在就可以得到一个 $\text{O}(n^2\log n)$ 的做法。

sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i ++) {
    for (int j = i + 1; j <= n; j ++) {
        ans += gcd(a[i], a[j]) * (n - j);
    }
}

那怎么优化呢?

一种优化思路是:仅枚举三元组中一个数,再用每种算法快速求出可能的三元组的值。

那枚举哪个数呢?

中间值:枚举出它之后,最大数的个数已知,且最小数的范围也已知。

那么我们需要快速求出的值就变为了 $\sum_{i = 1}^{j - 1} \gcd(a_i, a_j)$。

接着,我们对 $\gcd(a_i,a_j)$ 进行欧拉反演:$$ \gcd(a_i,a_j) = \sum_{d \mid \gcd(a_i, a_j)} \varphi(d) = \sum_{d \mid a_j} \varphi(d) [d \mid a_i] $$ 再将其代回原式:$$ \sum_{i = 1} ^ {j - 1} \sum_{d \mid a_j} \varphi(d)[d | a_i] $$ 将统计可以相约的循环移进来:$$ \sum_{d \mid a_j} \varphi(d) \sum_{i = 1}^{j - 1}[d | a_i] $$ 然后我们惊奇的发现,$ \sum_{i = 1}^{j - 1}[d | a_i]$ ,为小于 $a_j$ 的数中包含因数 $d$ 的数的个数,可以递推出来!

所以复杂度瓶颈仅在于 $\sum_{d \mid a_j} \varphi(d)$。

又因为 $\varphi(d)$ 可以用线性筛快速求出,且一个数的因数个数近似于 $\log(n)$。

所以时间复杂度近似于 $\text{O}(n+t\times\log(n))$,可以通过此题。

Code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 8e4 + 10, M = 1e5 + 10;
typedef long long ll;
ll n, t, ans;
ll a[N], pri[M], tot, phi[M], cnt[M];
vector<ll> evn[M];
bool isp[M];
void pre(int Max){//线性筛求欧拉函数
    for(int i = 1; i <= Max; i ++) isp[i] = 1;
    isp[1] = 0;
    phi[1] = 1;
    for(int i = 2; i <= Max; i ++){
        if(isp[i]){
            pri[++ tot] = i;
            phi[i] = i - 1;
        }
        for(int j = 1;j <= tot && i * pri[j] <= Max;j ++){
            isp[i * pri[j]] = 0;
            if(i % pri[j]) phi[i * pri[j]] = phi[i] * phi[pri[j]];//积性函数的性质
            else{
                phi[i * pri[j]] = phi[i] * pri[j];
                break;
            }
        }
    }
}

signed main() {
    ios::sync_with_stdio(0);
    for (int i = 1; i <= 1e5; i ++) {//预处理因数
        for (int j = 1; j * j <= i; j ++) {
            if (i % j == 0) {
                evn[i].push_back(j);
                if (j * j != i) evn[i].push_back(i / j);
            }
        }
    }
    pre(1e5);
    for (cin >> t; t; t --) {
        cin >> n;
        ans = 0;
        for (int i = 1; i <= 1e5; i ++) cnt[i] = 0;//多测清空
        for (int i = 1; i <= n; i ++) cin >> a[i];
        sort(a + 1, a + 1 + n);
        for (int i = 1; i <= n; i ++) {
            // cntd * phi[d] * (n - i)
            for (auto d : evn[a[i]]) ans += cnt[d] * phi[d] * (n - i);//上文推论
            for (auto d : evn[a[i]]) cnt[d] ++;//更新 cnt[d]
        }
        cout << ans << endl;
    }
    return 0;
}
posted @ 2023-11-28 23:08  固态H2O  阅读(4)  评论(0编辑  收藏  举报  来源