P5176 学习笔记

手刃一道黑的蒟蒻太高兴了!!!

题目传送门

给定三个整数 \(n,m,p\),求

\[\sum_{i=1}^n\sum_{j=1}^m\sum_{k=1}^p\gcd(ij,ik,jk)\cdot \gcd(i,j,k)\cdot \left(\frac{\gcd(i,j)}{\gcd(i,k)\cdot \gcd(j,k)}+\frac{\gcd(i,k)}{\gcd(i,j)\cdot \gcd(j,k)}+\frac{\gcd(j,k)}{\gcd(i,j)\cdot \gcd(i,k)}\right) \]

式子好长,看得蒟蒻都畏惧了。

注意到恒等式

\[\gcd(ij,ik,jk)=\frac{\gcd(i,j)\cdot\gcd(k,j)\cdot\gcd(i,k)}{\gcd(i,j,k)} \]

证明 要证 $$ \gcd(ab,bc,ca)=\frac{\gcd(a,b)\cdot\gcd(b,c)\cdot\gcd(c,a)}{\gcd(a,b,c)} $$ 令 $a,b,c$ 的质因数分解形式分别为 $$ a=\prod_p p^{a_p} $$ $$ b=\prod_p p^{b_p} $$ $$ c=\prod_p p^{c_p} $$ 对于 $\forall p$ 为质数,考虑左式与右式中 $p$ 的指数。

显然,左边 \(p\) 的指数为

\[\min(a_p+b_p,b_p+c_p,c_p+a_p) \]

右边 \(p\) 在分子中的指数为

\[\min(a_p,b_p)+\min(b_p,c_p)+\min(c_p,a_p) \]

在分母中的指数为

\[\min(a_p,b_p,c_p) \]

于是 \(p\) 在右式中的指数为

\[\min(a_p,b_p)+\min(b_p,c_p)+\min(c_p,a_p)-\min(a_p,b_p,c_p) \]

所以要证原命题,只需证

\[\min(a_p+b_p,b_p+c_p,c_p+a_p)=\min(a_p,b_p)+\min(b_p,c_p)+\min(c_p,a_p)-\min(a_p,b_p,c_p) \]

由于上式关于 \(a_p,b_p,c_p\) 轮换对称,不妨设 \(a_p \le b_p \le c_p\),则左式为

\[LHS=\min(a_p+b_p,b_p+c_p,c_p+a_p)=a_p+b_p \]

右式为

\[\begin{align*} RHS&=\min(a_p,b_p)+\min(b_p,c_p)+\min(c_p,a_p)-\min(a_p,b_p,c_p)\\ &=a_p+b_p+a_p-a_p=a_p+b_p\\ \end{align*} \]

所以左式等于右式,即原命题也成立,证毕。

我们将恒等式代入原式得 $$ \sum_{i=1}^n \sum_{j=1}^m \sum_{k=1}^p \sum_{cyc} \gcd(i,j)^2 $$ 其中 $\sum_{cyc}$ 表示轮换对称求和。

然后你就会发现这个东西的一部分就是 这个题\(k=2\) 的情况。所以你们知道这个题为什么评黑了吧。

\[F(s,t)=\sum_{i=1}^s \sum_{j=1}^t \gcd(i,j)^2 \]

则原式可化简为

\[pF(n,m)+nF(m,p)+mF(n,p) \]

考虑如何计算 \(F\),我们考虑对其进行莫反,即

\[F(s,t)=\sum_{k=1}^{\min(s,t)} \lfloor \frac sk \rfloor \lfloor \frac tk \rfloor G(k) \]

其中

\[G(k)=k^2 \prod_{p \mid k} (1-p^{-2}) \]

为积性函数。

线性筛预处理 + 整除分块即可。

code
#include <bits/stdc++.h>
#define pub public:
#define pri private:
#define fri friend:
#define Ofile(s) freopen(s".in", "r", stdin), freopen (s".out", "w", stdout)
#define Cfile(s) fclose(stdin), fclose(stdout)
#define fast ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;

using ll = long long;
using ull = unsigned long long;
using lb = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using pil = pair<int, ll>;
using pli = pair<ll, int>;

constexpr int mod = 1e9 + 7;
constexpr int maxn = 2e7 + 5;

ll n, m, p;
ll t, tot, maxnum;

ll G[maxn];
ll prime[maxn];
bool vis[maxn];

vector <tuple <ll, ll, ll> > query; // 离线

void sieve(ll limit) {
	G[1] = 1;
	for (ll i = 2; i <= limit; i++){
		if (!vis[i])
			prime[++tot] = i, G[i] = (i * i - 1) % mod;
		for (ll j = 1; j <= tot && i * prime[j] <= limit; j++){
			vis[i * prime[j]] = true;
			if (i % prime[j])
				G[i * prime[j]] = G[i] * G[prime[j]] % mod;
			else {
				G[i * prime[j]] = G[i] * prime[j] % mod * prime[j] % mod;
			}
		}
	}
	for (ll i = 1; i <= limit; i++)
		G[i] = (G[i - 1] + G[i]) % mod;
} // 线性筛

ll F(ll s, ll t){
	if (s > t)
		swap(s, t);
	ll res = 0;
	for (ll pl = 1, pr; pl <= s; pl = pr + 1){
		pr = min(s / (s / pl), t / (t / pl));
		ll term1 = (s / pl) * (t / pl) % mod;
		ll term2 = (G[pr] - G[pl - 1] + mod) % mod;
		res += term1 * term2 % mod, res %= mod;
	}
	return res;
} // 整除分块求 F(s,t) 的值

int main() {
	freopen("std.in", "r", stdin);
	freopen("std.out", "w", stdout);
	fast;
	cin >> t;
	while (t--){
		cin >> n >> m >> p;
		query.push_back({n, m, p});
		maxnum = max(max(maxnum, n), max(m, p));
	}
	sieve(maxnum); // 节约复杂度,就不直接把整个数组全筛了,只筛要用的
	for (auto [n, m, p] : query){
		cout << (p * F(n, m) % mod + n * F(m, p) % mod + m * F(n, p) % mod) % mod << endl; // 刚刚推的答案计算方法
	}
	return 0;
}
posted @ 2026-03-23 20:19  constexpr_ll  阅读(31)  评论(0)    收藏  举报