本题gcd暴力做会超时,因为给你一堆质数,你疯狂gcd,疯狂的递归辗转相除法,时间会超。
(三个数本题设为x y z)
如果暴力尝试过,也大概是排序,然后求和 gcd(x,y) * 后面的数的数目。
所以思路肯定是算前两个数。
目录
————
本题解是总结:Codeforces Round 911 (Div. 2)(A-E)思路讲解_哔哩哔哩_bilibili
浅显易懂滴讲解:
首先,本题数据范围是1~1e5,我们可以枚举出每个数的所有约数。(gcd不就是最大公divisor嘛)
(方法:枚举每个数,给它的所有倍数push_back自己即可。)
谁的约数有2: 2 4 6 8 10 ...
这样我们任何一个数的所有约数都统计好了。
统计x,y同约数出现的下标:
然后遍历输入的数组,每个数的所有约数都记录这个下标。然而我们不记录下标,记录n-1-i,其实是后面的数的数目,因为下标也是用来算这个的。
如何记录呢,起一个1~1e5的vector,push_back即可。这个操作还能让我们知道这个下标是第几次出现。
比如约数是3的:
3 3 3 3 3
第几次: 1 2 3 4 5
计算x,y同约数时,f(x,y,z)为这个约数的情况数:
接着就是计算了,比如3这个约数出现两次,说明两个数的公约数有3(我没说是最大公约数),我们另起一个数组ans,统计f(x,y,z)为3的情况数。这个答案是后面的数的数目乘以前面出现3的次数
比如约数是3的:
3 3 3 3 3
第几次: 1 2 3 4 5
^
这个是第三次,可以和前面组成两种,然后乘以后面的数的数目就是总的情况数,给ans[3]加上。
对情况数进行容斥去重:
比如:
// 3 6 18
// 3会出现3次
// 6只有两次
// 6和18的gcd是6不是3,但是我们把3多算一遍
// 所以3要减去6的情况数
我们要求的是gcd之和,乘以这个gcd:
ret += ans [ i ] * i;
代码:
#define int long long
const ll inf = 1e9;
const ll mx = 1e5;
//3 3 3 3
//
vector<int> divi[mx+2];//divisor//统计出范围内的每个数的约数
//用的时候调啊
void solve()
{
int n;
cin >> n;
vector<int>arr(n);
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
sort(arr.begin(), arr.end());
vector<int>darr[mx + 2];
for (int i = 0; i < n; i++)
{
//每个数的gcd后面的情况数
for (auto x : divi[arr[i]])
{
darr[x].push_back(n - 1 - i);
}
}
vector<int>ans(mx + 2);
for (int i = 1; i <= mx; i++)
{
int cnt = 0;
//第二个数开始才会加(共同gcd)
//cnt统计前面出现的次数
for (auto x : darr[i])
{
ans[i] += cnt * x;
cnt++;
}
}
//去重——容斥
for (int i = mx; i >= 1; i--)
{
for (int j = 2 * i; j <= mx; j += i)
{
ans[i] -= ans[j];
}
}
ll ret = 0;
for (int i = 1; i <= mx; i++)
{
ret += ans[i]*i;//前面求的是情况数,答案是所有gcd之和
}
cout << ret << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
for (int i = 1; i <= mx; i++)
{
for (int j = i; j <= mx; j += i)//我是我倍数的约数
{
divi[j].push_back(i);
}
}
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++)
{
solve();
}
return 0;
}