COMPFEST 13 Finals Online Mirror, cf-1575G. GCD Festival 题解

题意

给定一个n长度的数组a,要你计算

i=1nj=1ngcd(ai,aj)gcd(i,j)

思路

莫比乌斯反演(其实我觉得这里叫欧拉反演更合适些)

gcd(i,j)=d|id|jϕ(d)

代入原式

Ans=i=1nj=1ngcd(ai,aj)d|id|jϕ(d)

这里更换贡献计算顺序,枚举d来叠加答案的贡献

不过,在此之前,我们先约定一个符号

i=d,i+=dnf(d)

i=d 表示 i 的初始值 i+=d 表示 i 的步长(每次+d),上面那个 n 表示循环上界(in),相当于for(int i=d;i<=n;i+=d)

下面继续计算答案

Ans=i=1nj=1ngcd(ai,aj)d|id|jϕ(d)=d=1nϕ(d)i=d,i+=dnj=d,j+=dngcd(ai,aj)=d=1nϕ(d)i=d,i+=dnj=d,j+=dnx|aix|ajϕ(x)

同理,我们枚举因子 x,考虑什么时候ai,aj对答案有贡献

Ans=d=1nϕ(d)i=d,i+=dnj=d,j+=dnx|aix|ajϕ(x)=d=1nϕ(d)xϕ(x)i=d,i+=dnj=d,j+=dn[x|aix|aj]

其中,i=d,i+=dnj=d,j+=dn[x|aix|aj] 表示当 x 整除 aix 整除 aj 时,有1个贡献。那么我们就可以统计数组 a 中能被 x 整除的数有多少个,求平方(组合数学中的乘法原理)即是 x 当前带来的贡献

公式推导如下

个人感觉写的有点赘余,每一步都只有一小步的化简,但是考虑到数学公式的难以理解,还是写的很冗长,还望和我一样的初学者看的更明白些

(公式中,ϕ(x) 抄漏了, 感谢 猫萌不萌 的指正)

Ans=d=1nϕ(d)xϕ(x)i=d,i+=dnj=d,j+=dn[x|aix|aj]=d=1nϕ(d)xϕ(x)(i=d,i+=dnj=d,j+=dn[x|ai][x|aj])=d=1nϕ(d)xϕ(x)(i=d,i+=dn[x|ai]j=d,j+=dn[x|aj])=d=1nϕ(d)xϕ(x)(i=d,i+=dn[x|ai]i=d,i+=dn[x|ai])=d=1nϕ(d)xϕ(x)(i=d,i+=dn[x|ai])2

好了,到这里,我们就可以考虑如何枚举 x 了,显然枚举到 n 不太实际(复杂度O(n2)),我们考虑转而枚举每个元素的因子,这样的话就是 O(nlog nmax(|d(ai)|) ,其中n来自于枚举 dlog n来自于每次d枚举过程中选择的数(相关证明类似埃氏筛,与log的级数有关),|d(ai)| 表示 ai 的因子个数,也就是再去枚举每个数的因子(事先需要筛一次因数),因子个数大概也是log级别,这里由于知识面的不广暂不给出相关证明

代码

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

const i64 MOD = 1e9+7;
const int MX = 1e5+6;

vector<int> ph(MX);
vector<int> d[MX];

void get_phi() {
    for(int i=0;i<MX;++i) {
        ph[i] = i;
    }
    for(int i=2;i<MX;++i) {
        if(ph[i]==i)
            for(int j=i;j<MX;j+=i) {
                ph[j] -= ph[j]/i;
            }
    }

}
void get_divisor() {
    for(int i=1;i<MX;++i) {
        for(int j=i;j<MX;j+=i) {
            d[j].push_back(i);
        }
    }
}

int main(int argc, char const *argv[])
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    get_phi();
    get_divisor();

    int n;
    cin >> n;
    vector<i64> a(n+1);
    for(int i=1;i<=n;++i) {
        cin >> a[i];
    }

    i64 ans = 0;
    vector<int> cnt(MX);
    for(int i=1;i<=n;++i) {
        i64 s1=0;

        for(int j=i;j<=n;j+=i) {
            for(int e : d[a[j]]) {
                ++cnt[e];
            }
        }

        for(int j=i;j<=n;j+=i) {
            for(int e : d[a[j]]) {
                if(cnt[e]) {
                    i64 s2=cnt[e];
                    cnt[e] = 0;
                    s2 *= s2;
                    s2 %= MOD;
                    s2 *= ph[e];
                    s2 %= MOD;

                    s1 += s2;
                    s1 %= MOD;
                }
            }
        }

        ans += ph[i] * s1;
        ans %= MOD;
    }
    cout << ans;

    return 0;
}
posted @   LacLic  阅读(120)  评论(4编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示