转载自 OI-Wiki 。
「SPOJ 5971」LCMSUM
求值(多组数据)
∑ i = 1 n lcm ( i , n ) s.t. 1 ⩽ T ⩽ 3 × 1 0 5 , 1 ⩽ n ⩽ 1 0 6 \sum_{i=1}^n \operatorname{lcm}(i,n)\quad \text{s.t.}\ 1\leqslant T\leqslant 3\times 10^5,1\leqslant n\leqslant 10^6 i=1∑nlcm(i,n)s.t. 1⩽T⩽3×105,1⩽n⩽106
易得原式即
∑ i = 1 n i ⋅ n gcd ( i , n ) \sum_{i=1}^n \frac{i\cdot n}{\gcd(i,n)} i=1∑ngcd(i,n)i⋅n
将原式复制一份并且颠倒顺序,然后将 n 一项单独提出,可得
1 2 ⋅ ( ∑ i = 1 n − 1 i ⋅ n gcd ( i , n ) + ∑ i = n − 1 1 i ⋅ n gcd ( i , n ) ) + n \frac{1}{2}\cdot \left(\sum_{i=1}^{n-1}\frac{i\cdot n}{\gcd(i,n)}+\sum_{i=n-1}^{1}\frac{i\cdot n}{\gcd(i,n)}\right)+n 21⋅(i=1∑n−1gcd(i,n)i⋅n+i=n−1∑1gcd(i,n)i⋅n)+n
根据 gcd ( i , n ) = gcd ( n − i , n ) \gcd(i,n)=\gcd(n-i,n) gcd(i,n)=gcd(n−i,n),可将原式化为
1 2 ⋅ ( ∑ i = 1 n − 1 i ⋅ n gcd ( i , n ) + ∑ i = n − 1 1 i ⋅ n gcd ( n − i , n ) ) + n \frac{1}{2}\cdot \left(\sum_{i=1}^{n-1}\frac{i\cdot n}{\gcd(i,n)}+\sum_{i=n-1}^{1}\frac{i\cdot n}{\gcd(n-i,n)}\right)+n 21⋅(i=1∑n−1gcd(i,n)i⋅n+i=n−1∑1gcd(n−i,n)i⋅n)+n
两个求和式中分母相同的项可以合并。
1 2 ⋅ ∑ i = 1 n − 1 n 2 gcd ( i , n ) + n \frac{1}{2}\cdot \sum_{i=1}^{n-1}\frac{n^2}{\gcd(i,n)}+n 21⋅i=1∑n−1gcd(i,n)n2+n
即
1 2 ⋅ ∑ i = 1 n n 2 gcd ( i , n ) + n 2 \frac{1}{2}\cdot \sum_{i=1}^{n}\frac{n^2}{\gcd(i,n)}+\frac{n}{2} 21⋅i=1∑ngcd(i,n)n2+2n
可以将相同的 gcd ( i , n ) \gcd(i,n) gcd(i,n) 合并在一起计算,故只需要统计 gcd ( i , n ) = d \gcd(i,n)=d gcd(i,n)=d 的个数。当 gcd ( i , n ) = d \gcd(i,n)=d gcd(i,n)=d 时, gcd ( i d , n d ) = 1 \displaystyle\gcd(\frac{i}{d},\frac{n}{d})=1 gcd(di,dn)=1,所以 gcd ( i , n ) = d \gcd(i,n)=d gcd(i,n)=d 的个数有 φ ( n d ) \displaystyle\varphi(\frac{n}{d}) φ(dn) 个。
故答案为
1 2 ⋅ ∑ d ∣ n n 2 ⋅ φ ( n d ) d + n 2 \frac{1}{2}\cdot\sum_{d\mid n}\frac{n^2\cdot\varphi(\frac{n}{d})}{d}+\frac{n}{2} 21⋅d∣n∑dn2⋅φ(dn)+2n
变换求和顺序,设 d ′ = n d \displaystyle d'=\frac{n}{d} d′=dn,合并公因式,式子化为
1 2 n ⋅ ( ∑ d ′ ∣ n d ′ ⋅ φ ( d ′ ) + 1 ) \frac{1}{2}n\cdot\left(\sum_{d'\mid n}d'\cdot\varphi(d')+1\right) 21n⋅⎝⎛d′∣n∑d′⋅φ(d′)+1⎠⎞
设 g ( n ) = ∑ d ∣ n d ⋅ φ ( d ) \displaystyle \operatorname{g}(n)=\sum_{d\mid n} d\cdot\varphi(d) g(n)=d∣n∑d⋅φ(d),已知 g \operatorname{g} g 为积性函数,于是可以 Θ ( n ) \Theta(n) Θ(n) 筛出。每次询问 Θ ( 1 ) \Theta(1) Θ(1) 计算即可。
下面给出这个函数筛法的推导过程:
首先考虑 g ( p j k ) \operatorname g(p_j^k) g(pjk) 的值,显然它的约数只有 p j 0 , p j 1 , ⋯ , p j k p_j^0,p_j^1,\cdots,p_j^k pj0,pj1,⋯,pjk,因此
g ( p j k ) = ∑ w = 0 k p j w ⋅ φ ( p j w ) \operatorname g(p_j^k)=\sum_{w=0}^{k}p_j^w\cdot\varphi(p_j^w) g(pjk)=w=0∑kpjw⋅φ(pjw)
又有 φ ( p j w ) = p j w − 1 ⋅ ( p j − 1 ) \varphi(p_j^w)=p_j^{w-1}\cdot(p_j-1) φ(pjw)=pjw−1⋅(pj−1),则原式可化为
∑ w = 0 k p j 2 w − 1 ⋅ ( p j − 1 ) \sum_{w=0}^{k}p_j^{2w-1}\cdot(p_j-1) w=0∑kpj2w−1⋅(pj−1)
于是有
g ( p j k + 1 ) = g ( p j k ) + p j 2 k + 1 ⋅ ( p j − 1 ) \operatorname g(p_j^{k+1})=\operatorname g(p_j^k)+p_j^{2k+1}\cdot(p_j-1) g(pjk+1)=g(pjk)+pj2k+1⋅(pj−1)
那么,对于线性筛中的 g ( i ⋅ p j ) ( p j ∣ i ) \operatorname g(i\cdot p_j)(p_j|i) g(i⋅pj)(pj∣i),令 i = a ⋅ p j w ( gcd ( a , p j ) = 1 ) i=a\cdot p_j^w(\operatorname{gcd}(a,p_j)=1) i=a⋅pjw(gcd(a,pj)=1),可得
g ( i ⋅ p j ) = g ( a ) ⋅ g ( p j w + 1 ) \operatorname g(i\cdot p_j)=\operatorname g(a)\cdot\operatorname g(p_j^{w+1}) g(i⋅pj)=g(a)⋅g(pjw+1)
g ( i ) = g ( a ) ⋅ g ( p j w ) \operatorname g(i)=\operatorname g(a)\cdot\operatorname g(p_j^w) g(i)=g(a)⋅g(pjw)
即
g ( i ⋅ p j ) − g ( i ) = g ( a ) ⋅ p j 2 w + 1 ⋅ ( p j − 1 ) \operatorname g(i\cdot p_j)-\operatorname g(i)=\operatorname g(a)\cdot p_j^{2w+1}\cdot(p_j-1) g(i⋅pj)−g(i)=g(a)⋅pj2w+1⋅(pj−1)
同理有
g ( i ) − g ( i p j ) = g ( a ) ⋅ p j 2 w − 1 ⋅ ( p j − 1 ) \operatorname g(i)-\operatorname g(\frac{i}{p_j})=\operatorname g(a)\cdot p_j^{2w-1}\cdot(p_j-1) g(i)−g(pji)=g(a)⋅pj2w−1⋅(pj−1)
因此
g ( i ⋅ p j ) = g ( i ) + ( g ( i ) − g ( i p j ) ) ⋅ p j 2 \operatorname g(i\cdot p_j)=\operatorname g(i)+\left (\operatorname g(i)-\operatorname g(\frac{i}{p_j})\right )\cdot p_j^2 g(i⋅pj)=g(i)+(g(i)−g(pji))⋅pj2
时间复杂度: Θ ( n + T ) \Theta(n+T) Θ(n+T)
代码
#include <cstdio>
const int N = 1000000;
int tot, p[N + 5];
long long g[N + 5];
bool flg[N + 5]; //标记数组
void solve() {
g[1] = 1;
for (int i = 2; i <= N; ++i) {
if (!flg[i]) {
p[++tot] = i;
g[i] = (long long)1 * i * (i - 1) + 1;
}
for (int j = 1; j <= tot && i * p[j] <= N; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
g[i * p[j]] =
g[i] + (g[i] - g[i / p[j]]) * p[j] * p[j]; //代入推出来的式子
break;
}
g[i * p[j]] = g[i] * g[p[j]];
}
}
}
int main() {
int T, n;
solve(); //预处理g数组
scanf("%d", &T);
for (int i = 1; i <= T; ++i) {
scanf("%d", &n);
printf("%lld\n", (g[n] + 1) * n / 2);
}
return 0;
}