CF1900D - Small GCD【数论超好题】

CF1900D - Small GCD【数论超好题】

前置知识

正题

题意

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

思路

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

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

显然,我们现在就可以得到一个 O(n2logn) 的做法。

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);
    }
}

那怎么优化呢?

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

那枚举哪个数呢?

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

那么我们需要快速求出的值就变为了 i=1j1gcd(ai,aj)

接着,我们对 gcd(ai,aj) 进行欧拉反演:gcd(ai,aj)=dgcd(ai,aj)φ(d)=dajφ(d)[dai] 再将其代回原式:i=1j1dajφ(d)[d|ai] 将统计可以相约的循环移进来:dajφ(d)i=1j1[d|ai] 然后我们惊奇的发现,i=1j1[d|ai] ,为小于 aj 的数中包含因数 d 的数的个数,可以递推出来!

所以复杂度瓶颈仅在于 dajφ(d)

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

所以时间复杂度近似于 O(n+t×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 @   固态H2O  阅读(11)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示