CF 1900 D Small GCD

题意翻译

        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 。

        即求\sum_{i=1}^{n} \sum_{j=i+1}^{n} \sum_{k=j+1}^{n} f(a_{i},a_{j},a_{k})

输入格式

        第一行一个整数 t (1≤t≤10) 为数据组数。

        每组数据第一行为 n (3≤n≤8⋅1e4),第二行为 a1​,a2​,…,an​ (1≤ai​≤1e5) 。

        所有数据中 n 的和不超过 8⋅1e4。

输出样例

思路

错误:

         a_{i}的取值范围是 1 \leq a_{i} \leq 10^{5},所以我们找出 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";
}

正确:

        首先,顺序不会影响到三元组的情况,所以先排序。(很难解释,也可以认为是 C_{n}^{3} 选出的三元组不管改不改变顺序,都是一样的,组内顺序反正没关系)。

        答案可以转化为\sum_{i=1}^{n} \sum_{j=i+1}^{n} (n-j)*gcd(a_{i},a_{j})

        因为 a_{i}的取值范围是 1 \leq a_{i} \leq 10^{5},所以 a_{i} 的因数最多不超过 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值