D. Small GCD

D. Small GCD

Let a, b, and c be integers. We define function f(a,b,c) as follows:

Order the numbers a, b, c in such a way that abc. Then return gcd(a,b), where gcd(a,b) denotes the greatest common divisor (GCD) of integers a and b.

So basically, we take the gcd of the 2 smaller values and ignore the biggest one.

You are given an array a of n elements. Compute the sum of f(ai,aj,ak) for each i, j, k, such that 1i<j<kn.

More formally, compute i=1nj=i+1nk=j+1nf(ai,aj,ak).

Input

Each test contains multiple test cases. The first line contains the number of test cases t (1t10). The description of the test cases follows.

The first line of each test case contains a single integer n (3n8104) — length of the array a.

The second line of each test case contains n integers, a1,a2,,an (1ai105) — elements of the array a.

It is guaranteed that the sum of n over all test cases does not exceed 8104.

Output

For each test case, output a single number — the sum from the problem statement.

Example

input

2
5
2 3 6 12 17
8
6 12 8 10 15 12 18 16

output

24
203

Note

In the first test case, the values of f are as follows:

i=1, j=2, k=3, f(ai,aj,ak)=f(2,3,6)=gcd(2,3)=1;

i=1, j=2, k=4, f(ai,aj,ak)=f(2,3,12)=gcd(2,3)=1;

i=1, j=2, k=5, f(ai,aj,ak)=f(2,3,17)=gcd(2,3)=1;

i=1, j=3, k=4, f(ai,aj,ak)=f(2,6,12)=gcd(2,6)=2;

i=1, j=3, k=5, f(ai,aj,ak)=f(2,6,17)=gcd(2,6)=2;

i=1, j=4, k=5, f(ai,aj,ak)=f(2,12,17)=gcd(2,12)=2;

i=2, j=3, k=4, f(ai,aj,ak)=f(3,6,12)=gcd(3,6)=3;

i=2, j=3, k=5, f(ai,aj,ak)=f(3,6,17)=gcd(3,6)=3;

i=2, j=4, k=5, f(ai,aj,ak)=f(3,12,17)=gcd(3,12)=3;

i=3, j=4, k=5, f(ai,aj,ak)=f(6,12,17)=gcd(6,12)=6.

The sum over all triples is 1+1+1+2+2+2+3+3+3+6=24.
In the second test case, there are 56 ways to choose values of i, j, k. The sum over all f(ai,aj,ak) is 203.

 

解题思路

  首先可以对 a 从小到大排序,不会影响结果。这是因为对原本 a 中所有的三元组逐个排序,与排序后 a 的所有三元组是完全一样的。然后是因为统计答案,所以尝试对所有的三元组按照某个属性分类再分别统计。这里给出两种不同的分类方法。

  先给出我的做法,按照三元组中间的值进行分类,那么一共被分成 n2 类,即中间值分别为 a2an1 的三元组。暴力做法是枚举 ai 作为中间值,那么所有比 ai 小(或相等)的值就是 aj,j[1,i1],所有比 ai 大(或相等)的值有 ni 个,枚举所有的 aj 分别计算 gcd(ai,aj)×(ni),表示所有最小值为 aj,中间值为 ai,最大值比 ai 大(或相等)的三元组的贡献。那么 j=1i1gcd(ai,aj)×(ni) 就是所有中间值为 ai 的三元组的贡献,最终答案就是 i=2n1j=1i1gcd(ai,aj)×(ni),很显然时间复杂度为 O(n2logm) 会超时,其中 m=max1in{ai}

  当确定了 ai 作为中间值后,那么 ai 与任意数的最大公约数只可能为 ai 的约数。所以可以反过来考虑,从大到小枚举 ai 的所有约数 d,如果发现有之前没选过的 aj 满足 daj,那么对于这些 ajgcd(ai,aj)=d,选择这些 aj。由于在不超过 105 的数中一个数的约数个数最多有 128 个,因此这种做法应该是可行的。关键是在于如何维护 ai 的每个约数对应哪些 aj

  我们先通过 O(mlogm) 的时间复杂度预处理出来 1m 的每个数的约数。维护数组 cntx,表示在枚举到 aia1ai1 中含约数 x 的数的个数,当枚举完 ai 后遍历 ai 每个约数 d,有 cntdcntd+1。令 cnt 表示 cnt 的备份,对于 ai,从大到小枚举其约数 d,如果 cntd>0 说明前面存在没选过且约数为 daj,有 cntd 个,那么贡献就是 d×cntd×(ni)。将这些 aj 标记为选过等价于将这些 aj 的记录从 cnt 抹去,做法是枚举每个 aj 的约数 x,然后 cntxcntx1,然而我们只关心 ai 的约数,所以在 aj 的所有约数中只用删去同时是 ai 的约数即可。而既是 aj 的约数,又是 ai 的约数,其实就是 aiaj所有公约数,即 gcd(ai,aj)=d 的所有约数(xy 的所有公约数,其实就是 gcd(x,y) 的所有约数)。所以我们只需枚举 d 的所有约数 x,然后 cntxcntxcntd

  1m 中平均每个数含有 O(logm) 个约数,所以估计时间复杂度为 O(mlogm+n(logn+log2m))。这时间复杂度肯定是不准确的,但直觉上感觉不高毕竟一个数含有的约数数量还是很少的,麻烦知道的大佬在评论区留言 qwq。

  AC 代码如下:

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

typedef long long LL;

const int N = 1e5 + 10;

int a[N];
vector<int> ds[N];
int cnt[N], bp[N];

void init() {
    for (int i = 1; i < N; i++) {
        for (int j = i; j < N; j += i) {
            ds[j].push_back(i);
        }
    }
    for (int i = 1; i < N; i++) {
        reverse(ds[i].begin(), ds[i].end());
    }
}

void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    sort(a + 1, a + n + 1);
    LL ret = 0;
    memset(cnt, 0, sizeof(cnt));
    for (int i = 1; i < n; i++) {
        for (auto &x : ds[a[i]]) {
            bp[x] = cnt[x];
        }
        for (auto &x : ds[a[i]]) {
            int t = bp[x];
            if (t) {
                ret += 1ll * x * t * (n - i);
                for (auto &y : ds[x]) {
                    bp[y] -= t;
                }
            }
        }
        for (auto &x : ds[a[i]]) {
            cnt[x]++;
        }
    }
    printf("%lld\n", ret);
}

int main() {
    init();
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

  另外一种是官方题解给出的做法,按照三元组中最小的两个数的最大公约数进行分类,那么一共被分成 m 类,m=max1in{ai}。大致思路是求出最大公约数恰好i 的三元组数量 f(i),那么最终答案就是 i=1mi×f(i)

  关键在于如何求 f(i),做法与 D. Counting Rhyme 类似,这题求的是所有最大公约数恰好为 i 的二元组的数量。

  首先如果三元组的最大公约数要为 d,那么三元组中最小值和中间值必然是 d 的倍数,我们将所有满足 dai 的下标 i 筛出来,假设有 k 个这样的下标。那么从这 k 个下标中任意选择两个出来,这两个下标对应的数的最大公约数必然是 d 的倍数。先求最大公约数是 d 的倍数的三元组的数量。从小到大枚举这些下标,假设第 i 个下标是 pi,将 api 作为中间值,前 i1 个下标的数作为最小值,最大值可选的数有 npi 个,则最大公约数是 d 的倍数且中间值为 api 的三元组的数量就是 (i1)×(npi),因此最大公约数是 d 的倍数的三元组的数量就是 s=i=2k(i1)×(npi)

  可以知道最大公约数是 d 的倍数的三元组数量本质是由最大公约数恰好为 d 的三元组的数量、最大公约数恰好为 2d 的三元组的数量、......构成的,即 s=i=1mdf(id),从而推出 f(d)=si=2mdf(id)。对此我们可以从大到小倒着枚举 d 来求 f(d)

  AC 代码如下,时间复杂度为 O(nlogn+nm+mlogm)

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

typedef long long LL;

const int N = 1e5 + 10;

int a[N];
vector<int> p[N];
LL f[N];

void solve() {
    int n, m = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        m = max(m, a[i]);
    }
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= m; i++) {
        p[i].clear();
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j * j <= a[i]; j++) {
            if (a[i] % j == 0) {
                p[j].push_back(i);
                if (a[i] / j != j) p[a[i] / j].push_back(i);
            }
        }
    }
    LL ret = 0;
    for (int i = m; i; i--) {
        f[i] = 0;
        for (int j = 1; j < p[i].size(); j++) {
            f[i] += 1ll * j * (n - p[i][j]);
        }
        for (int j = i + i; j <= m; j += i) {
            f[i] -= f[j];
        }
        ret += i * f[i];
    }
    printf("%lld\n", ret);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  Codeforces Round 911 (Div. 2) Editorial:https://codeforces.com/blog/entry/122677

posted @   onlyblues  阅读(210)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
历史上的今天:
2022-11-27 Count Subarrays With Median K
Web Analytics
点击右上角即可分享
微信分享提示