题解:CF1900D(Small GCD)

题解:CF1900D(Small GCD)

题目传送门:虫洞入口。

先简单地说一下题目大义,给你t组长度为n的整数数组a,让你求出a中每三个数组成的三元组(下标相同顺序不同算成一种情况)中较小的两个数的GCD的和。数学家门喜欢的表达为,其中,我们定义为a、b、c中最小的两个数的GCD。

这道题是一道普通的数论题,我用了4个多小时才做出来了……大多数人都是用欧拉反演做的,作为一个初一的小朋友,根本无法理解,所以我参考了洛谷上QueueLi的优秀题解,得到了一个人类幼崽能够理解的思路——容斥原理。

我们先从最朴素的做法想起,不太用脑子的人就能想到三层循环的遍历,按照公式求解,时间复杂度,O(n^3),妥妥的TLE。

接着我们考虑优化掉一个n,由于我们在取数的时候不会考虑出现的顺序,所以我们可以给a先排个序,那么我们考虑遍历最小的两个数,剩下的最大的数自然为次小数右侧的数的数量(当然我们指的是排序后的数组),我们计算最小数和次小数的GCD,最后乘以次小数右侧的数的数量(n减去次小数的下标)。时间复杂度O(n^2),还是TLE,如果是CSP-J考试就可以尝逝写这个部分分。

最后,来到了正解,还请大家的耐心还能够支撑你们看到这里的话,恭喜你们找到了一个比较好理解的题解。

我们知道,根据调和级数的一些性质,我们可以用O(m*log(m))(m是指100000,即a的最大值)的时间得出每一个数的每一个因数,让因数从大到小排序(其实正常做,最后把每个数的因数翻转即可,具体见代码),对以后很有用。

定义两个数组:s[i]表示目前为止有多少个数含有因数i,tt[i]表示目前为止有多少个数与a[i]的GCD为i,当然,tt数组需要用容斥原理做,具体的,我们从大到小遍历a[i]的每个因数,对于每个因数(记作j),先把tt[j]设为s[j],之后需要从tt[j]中把所有既是j的倍数(不包括j本身)又是a[i]的因数的数(记作j*k)所对应的tt[j*k]减去,因为j*k才是a[i]与这些数的GCD,而不是j。想要得到j*k,只需要遍历a[i]/j的每一个因数,因为这些数肯定是j的倍数(k当然是整数,所以j*k是j的倍数),并且一定是a[i]的约数(它都是a[i]/j的因数了,还能不是a[i]的因数吗),当然,“1”要特判。在遍历每个a[i]的因数的最后,要把它的s值加一,因为目前多了一个a[i]是a[i]的这个因数的倍数。

我们姑且把答案叫做ans,我们还需要一个变量sum进行累加,记录的是目前为止所有的两个数组成的数对的GCD的和,因为在这些数对后面加上一个无关紧要的最大值a[i]也不会影响数值,只是和a[i]组成一个完整的三元组。 sum要累加的就是每个a[i]的因数对应的tt[j]*j,表示GCD为j的数的个数乘上j的值,即所有GCD为j的数的和。而ans就是累加每一个i时刻的sum。

最后输出ans即可,如果算不明白(比如说我)就都打开long long,不要忘了n的范围和a的范围是不同的。

相信大家最想要的一定是代码,那么AC code走起!

#include
#define A 110000
#define N 88000
using namespace std;
const long long _a=100000;
vectorfactor[A]={};
long long a[N]={},s[A]={},tt[A]={},ans=0,n=0,t=0;
int main(){
    for(long long i=1;i<=_a;i++){
        for(long long j=i;j<=_a;j+=i){
            factor[j].push_back(i);
        }
    }
    for(long long i=1;i<=_a;i++){
        reverse(factor[i].begin(),factor[i].end());
    }
    scanf("%lld",&t);
    while(t--){
        memset(s,0,sizeof(s));
        ans=0;
        scanf("%lld",&n);
        for(long long i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        sort(a+1,a+1+n);
        long long sum=0;
        for(long long i=1;i<=n;i++){
            ans+=sum;
            for(long long j:factor[a[i]]){
                tt[j]=s[j];
                for(long long k:factor[a[i]/j]){
                    tt[j]-=k==1?0:tt[j*k];
                }
                sum+=tt[j]*j;
                s[j]++;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

总共41行,时间复杂度,x是一个数的因数最大是多少个,那么这个值应该是128,2s应该能跑完。

你可能感兴趣的:(c++,数学)