D. Small GCD
D. Small GCD
Let , , and be integers. We define function as follows:
Order the numbers , , in such a way that . Then return , where denotes the greatest common divisor (GCD) of integers and .
So basically, we take the of the smaller values and ignore the biggest one.
You are given an array of elements. Compute the sum of for each , , , such that .
More formally, compute
Input
Each test contains multiple test cases. The first line contains the number of test cases (). The description of the test cases follows.
The first line of each test case contains a single integer () — length of the array .
The second line of each test case contains integers, () — elements of the array .
It is guaranteed that the sum of over all test cases does not exceed .
Output
For each test case, output a single number — the sum from the problem statement.
Example
input
2
5
2 3 6 12 17
8
6 12 8 10 15 12 18 16
output
24
203
Note
In the first test case, the values of are as follows:
, , , ;
, , , ;
, , , ;
, , , ;
, , , ;
, , , ;
, , , ;
, , , ;
, , , ;
, , , .
The sum over all triples is .
In the second test case, there are ways to choose values of , , . The sum over all is .
解题思路
首先可以对 从小到大排序,不会影响结果。这是因为对原本 中所有的三元组逐个排序,与排序后 的所有三元组是完全一样的。然后是因为统计答案,所以尝试对所有的三元组按照某个属性分类再分别统计。这里给出两种不同的分类方法。
先给出我的做法,按照三元组中间的值进行分类,那么一共被分成 类,即中间值分别为 的三元组。暴力做法是枚举 作为中间值,那么所有比 小(或相等)的值就是 ,所有比 大(或相等)的值有 个,枚举所有的 分别计算 ,表示所有最小值为 ,中间值为 ,最大值比 大(或相等)的三元组的贡献。那么 就是所有中间值为 的三元组的贡献,最终答案就是 ,很显然时间复杂度为 会超时,其中 。
当确定了 作为中间值后,那么 与任意数的最大公约数只可能为 的约数。所以可以反过来考虑,从大到小枚举 的所有约数 ,如果发现有之前没选过的 满足 ,那么对于这些 有 ,选择这些 。由于在不超过 的数中一个数的约数个数最多有 个,因此这种做法应该是可行的。关键是在于如何维护 的每个约数对应哪些 。
我们先通过 的时间复杂度预处理出来 的每个数的约数。维护数组 ,表示在枚举到 时 中含约数 的数的个数,当枚举完 后遍历 每个约数 ,有 。令 表示 的备份,对于 ,从大到小枚举其约数 ,如果 说明前面存在没选过且约数为 的 ,有 个,那么贡献就是 。将这些 标记为选过等价于将这些 的记录从 抹去,做法是枚举每个 的约数 ,然后 ,然而我们只关心 的约数,所以在 的所有约数中只用删去同时是 的约数即可。而既是 的约数,又是 的约数,其实就是 和 的所有公约数,即 的所有约数( 和 的所有公约数,其实就是 的所有约数)。所以我们只需枚举 的所有约数 ,然后 。
中平均每个数含有 个约数,所以估计时间复杂度为 。这时间复杂度肯定是不准确的,但直觉上感觉不高毕竟一个数含有的约数数量还是很少的,麻烦知道的大佬在评论区留言 qwq。
AC 代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
vector<int> ds[N];
int cnt[N], bp[N];
void init() {
for (int i = 1; i < N; i++) {
for (int j = i; j < N; j += i) {
ds[j].push_back(i);
}
}
for (int i = 1; i < N; i++) {
reverse(ds[i].begin(), ds[i].end());
}
}
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
sort(a + 1, a + n + 1);
LL ret = 0;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i < n; i++) {
for (auto &x : ds[a[i]]) {
bp[x] = cnt[x];
}
for (auto &x : ds[a[i]]) {
int t = bp[x];
if (t) {
ret += 1ll * x * t * (n - i);
for (auto &y : ds[x]) {
bp[y] -= t;
}
}
}
for (auto &x : ds[a[i]]) {
cnt[x]++;
}
}
printf("%lld\n", ret);
}
int main() {
init();
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
另外一种是官方题解给出的做法,按照三元组中最小的两个数的最大公约数进行分类,那么一共被分成 类,。大致思路是求出最大公约数恰好为 的三元组数量 ,那么最终答案就是 。
关键在于如何求 ,做法与 D. Counting Rhyme 类似,这题求的是所有最大公约数恰好为 的二元组的数量。
首先如果三元组的最大公约数要为 ,那么三元组中最小值和中间值必然是 的倍数,我们将所有满足 的下标 筛出来,假设有 个这样的下标。那么从这 个下标中任意选择两个出来,这两个下标对应的数的最大公约数必然是 的倍数。先求最大公约数是 的倍数的三元组的数量。从小到大枚举这些下标,假设第 个下标是 ,将 作为中间值,前 个下标的数作为最小值,最大值可选的数有 个,则最大公约数是 的倍数且中间值为 的三元组的数量就是 ,因此最大公约数是 的倍数的三元组的数量就是 。
可以知道最大公约数是 的倍数的三元组数量本质是由最大公约数恰好为 的三元组的数量、最大公约数恰好为 的三元组的数量、......构成的,即 ,从而推出 。对此我们可以从大到小倒着枚举 来求 。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
vector<int> p[N];
LL f[N];
void solve() {
int n, m = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
m = max(m, a[i]);
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= m; i++) {
p[i].clear();
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= a[i]; j++) {
if (a[i] % j == 0) {
p[j].push_back(i);
if (a[i] / j != j) p[a[i] / j].push_back(i);
}
}
}
LL ret = 0;
for (int i = m; i; i--) {
f[i] = 0;
for (int j = 1; j < p[i].size(); j++) {
f[i] += 1ll * j * (n - p[i][j]);
}
for (int j = i + i; j <= m; j += i) {
f[i] -= f[j];
}
ret += i * f[i];
}
printf("%lld\n", ret);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
参考资料
Codeforces Round 911 (Div. 2) Editorial:https://codeforces.com/blog/entry/122677
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17859984.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2022-11-27 Count Subarrays With Median K