Small GCD
比较新, 没见过.
题意
求 \(\displaystyle \sum_{i = 1}^n \sum_{j = i+1}^n \sum_{k =j +1}^n f(a_i, a_j, a_k)\), \(f(a_i, a_j, a_k)\) 表示的是较小两个数的 \(\gcd\).
其中 \(n \le 8 \times 10^4, a_i \le 10^5\).
思路
直接对原式子求和是 \(\mathcal{O}(n^3 \log V)\) 的, 考虑将 \(a\) 数组排序, 我们枚举 \(f(a_i, a_j, a_k)\) 中第二大的数 \((\)也就是 \(a_j)\), 答案就成了 \(\displaystyle \sum_{j = 1}^n (n - j) \sum_{i = 1}^{j - 1} \gcd(a_i, a_j)\) 其中乘的 \((n - j)\) 是因为 \(a_k\) 可以在 \([j + 1, n]\) 中任取.
瓶颈在于快速计算 \(\displaystyle \sum_{i = 1}^{j - 1} \gcd(a_i, a_j)\).
我们令 \(f_k\) 表示在 \(1 \sim j - 1\) 中是 \(k\) 的倍数的有多少个, \(g_k\) 表示在 \(1 \sim j - 1\) 中满足 \(\gcd(a_i, a_j) = k\) 的数有多少个. \(f_k\) 可以直接枚举算出, \(g_k\) 考虑使用容斥减去它和它倍数重复的部分即可.
void init() {for (int i = 1; i <= mx; ++i) v[i].clear(), f[i] = g[i] = 0;mx = 0;scanf("%lld", &n);for (int i = 1; i <= n; ++i) scanf("%lld", a + i), mx = max(mx, a[i]);sort(a + 1, a + n + 1);for (int i = mx; i; --i)for (int j = i; j <= mx; j += i) v[j].push_back(i);
}void calculate() {int ans = 0;for (int i = 1; i <= n; ++i) {for (int j : v[a[i]]) g[j] = f[j]++;for (int j : v[a[i]]) for (int k : v[j]) if (k ^ j) g[k] -= g[j];for (int j : v[a[i]]) ans += (n - i) * g[j] * j;}printf("%lld\n", ans);
}
结语
数学方面还是不够强, 得练.