题意翻译
a, b, c 为整数,定义 f(a,b,c) 如下:
将三个数排序,使得 a≤b≤c ,f(a,b,c) = gcd(a,b) 。简而言之,函数返回较小的两个数的最大公约数。
给你一个数组 a ,包含 n 个元素。求所有 f(ai,aj,ak) 之和,其中 1≤i<j<k≤n 。
即求。
输入格式
第一行一个整数 t (1≤t≤10) 为数据组数。
每组数据第一行为 n (3≤n≤8⋅1e4),第二行为 a1,a2,…,an (1≤ai≤1e5) 。
所有数据中 n 的和不超过 8⋅1e4。
输出样例
思路
错误:
的取值范围是 ,所以我们找出 1~1e5 的所有素数 prime[i] ,如果 a 数组中有超过 2 个数字有 prime[i] 这个因数,那么这些数字的最大公约数是 prime[i] 的倍数。
两个数字的最大公约数不一定是质数,所以对于每一个 i ,我们都操作到数组 a 里面只有 0 个或 1 个数字有这个因数为止。
问题是两个数字的最大公约数是 1 的时候没有加进去,而且加不进去/(ㄒoㄒ)/~~
void solve(){
cin>>n; ans=0;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
//遍历所有可能的质因数
for(int i=0;prime[i]<=a[n];i++){
num=0;
//遍历a数组
for(int j=1;j<=n;j++){
if(a[j]%prime[i]==0){
num++;
//前面有(num-1)个数字可以和a[j]有这个因数,这样的a[?],a[j],后面可以接(n-j)个a[k]
ans+=(num-1)*prime[i]*(n-j); a[j]/=prime[i];
}
}
if(num>1) i--;
}
cout<<ans<<"\n";
}
正确:
首先,顺序不会影响到三元组的情况,所以先排序。(很难解释,也可以认为是 选出的三元组不管改不改变顺序,都是一样的,组内顺序反正没关系)。
答案可以转化为
因为 的取值范围是 ,所以 的因数最多不超过 128 个,通过容斥原理扣除它与它的倍数之间的重复情况去暴力。
具体讲就是 g[x] 表示 a[1]~a[i-1] 中有 x 这个因数的数量
for(int j=1;j<i;j++) if(a[j]%x==0) g[x]++; //仅供理解,真实代码不是这样
f[x] 表示 a[i] 与 a[1]~a[i-1] 中以 x 这个因数为最大公约数的数量
for(int j=1;j<i;j++) if(gcd(a[i],a[j])==x) f[x]++; //仅供理解,真实代码不是这样
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 7;
int n, a[N], f[N], g[N];
ll ans, tmp;
vector<int> fac[N];
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n); tmp=0; ans=0;
for(int i=0;i<=a[n];i++) g[i]=0, f[i]=0;
for(int i=1;i<=n;i++){
//每次tmp不需要归0,因为a[?]和a[??]配上这个a[i](共三个)还要再加一次
ans+=tmp;//总共去三个数,第二个不可能是a[n],所以在前面加,i=n的时候不加
for(auto u:fac[a[i]]){
f[u]=g[u];
//假设a[i]=32,u=8,那么我们要减掉约数为16,32的
for(auto v:fac[a[i]/u]){
if(v!=1) f[u]-=f[u*v];
}
tmp+=(ll)u*f[u];
}
for(auto u:fac[a[i]]) g[u]++;
}
cout<<ans<<"\n";
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for(int i=1;i<=1e5;i++){
for(int j=i;j<=1e5;j+=i) fac[j].push_back(i);
}
for(int i=1;i<=1e5;i++){
reverse(fac[i].begin(),fac[i].end());
}
int Case=1;
cin>>Case;
while(Case--) solve();
return 0;
}