P2398 GCD SUM 题解

博客园同步

原题链接

前置知识:

整除分块

线性筛模板

算法一

对于 30 % 30 \% 30% 的数据, n ≤ 3 × 1 0 3 n \leq 3 \times 10^3 n3×103.

直接模拟就好了。

时间复杂度: O ( n 2 log ⁡ n ) O(n^2 \log n) O(n2logn). 实际得分: 30 p t s 30pts 30pts.

for(i from 1 to n)
for(j from 1 to n)
	s+=gcd(i,j)
print(s)	

算法二

对于 60 % 60 \% 60% 的数据, 7000 ≤ n ≤ 7100 7000 \leq n \leq 7100 7000n7100.

注意到 O ( n 2 log ⁡ n ) O(n^2 \log n) O(n2logn) 无法通过了。但是,我们可以计算出 n = 7000 n = 7000 n=7000 的答案为 275797760 275797760 275797760. 然后,对于 7001 7001 7001 ~ n n n 区间的答案,实际上就是 两倍的该区间与 1 ≤ i ≤ 7000 1 \leq i \leq 7000 1i7000 的两两 gcd ⁡ \gcd gcd 之和,再加上自己和自己区间答案。

???

就比方说, a a a b b b 表示区间,定义 a × b a \times b a×b a a a 区间与 b b b 区间两两 gcd ⁡ \gcd gcd 和。那么, x = a + b x = a + b x=a+b,则 x 2 = ( a + b ) 2 = a 2 + 2 × a × b + b 2 x^2 = (a+b)^2 = a^2 + 2 \times a \times b + b^2 x2=(a+b)2=a2+2×a×b+b2.

a 2 a^2 a2 的答案被提前算出,然后 b 2 b^2 b2 区间长度 ≤ 100 \leq 100 100 结束, a × b a \times b a×b 计算也只需要 7000 × 100 = 7 × 1 0 5 7000 \times 100 = 7 \times 10^5 7000×100=7×105 再加个 log ⁡ \log log 的时间,过了。

时间复杂度: O ( wys ) O(\text{wys}) O(wys). 实际得分: 60 p t s 60pts 60pts.

s=275797760
for(i from 1 to 7000)
for(j from 7001 to n)
	s+=gcd(i,j)*2
for(i from 7001 to n)
for(j from 7001 to n)
	s+=gcd(i,j)
print(s)		

算法三

显然本题可以用 ϕ \phi ϕ 过,但是我们用 莫比乌斯反演

优化时间复杂度常常需要从零开始。 —— 《深入浅出程序设计竞赛 - 基础篇》 某句改编

对,下面是推式子时间。

∑ i = 1 n ∑ j = 1 n gcd ⁡ ( i , j ) \sum_{i=1}^n \sum_{j=1}^n \gcd(i,j) i=1nj=1ngcd(i,j)

= ∑ d = 1 n d ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = = d ] = \sum_{d=1}^n d \sum_{i=1}^n \sum_{j=1}^n [\gcd(i,j)==d] =d=1ndi=1nj=1n[gcd(i,j)==d]

= ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ n d ⌋ [ gcd ⁡ ( i , j ) = = 1 ] = \sum_{d=1}^n d \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{n}{d} \rfloor} [\gcd(i,j)==1] =d=1ndi=1dnj=1dn[gcd(i,j)==1]

= ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ n d ⌋ ∑ k ∣ gcd ⁡ ( i , j ) μ k = \sum_{d=1}^n d \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{k|\gcd(i,j)} \mu_k =d=1ndi=1dnj=1dnkgcd(i,j)μk

= ∑ d = 1 n d ∑ k = 1 n ⌊ n T ⌋ 2 μ k ( T = d × k ) = \sum_{d=1}^n d \sum_{k=1}^n \lfloor \frac{n}{T} \rfloor^2 \mu_k (T = d \times k) =d=1ndk=1nTn2μk(T=d×k)

= ∑ T = 1 n ∑ d ∣ T d × μ T d ⌊ n T ⌋ 2 = \sum_{T=1}^n \sum_{d|T} d \times \mu_\frac{T}{d} \lfloor \frac{n}{T} \rfloor^2 =T=1ndTd×μdTTn2

= ∑ T = 1 n ϕ ( T ) ⌊ n T ⌋ 2 = \sum_{T=1}^n \phi(T) \lfloor \frac{n}{T} \rfloor^2 =T=1nϕ(T)Tn2

每一步都是运用了 μ \mu μ 的基本性质,对最后一步的操作讲解一下:

你发现 ∑ d ∣ T d × μ T d \sum_{d|T} d \times \mu_{\frac{T}{d}} dTd×μdT 这玩意儿很像 I d ∗ μ Id * \mu Idμ然后就是了,而 I d ∗ μ = ϕ Id * \mu = \phi Idμ=ϕ,是不是很神奇? 实际上是一个隐蔽的性质

推式子的基本要点总结:

  1. 枚举 gcd ⁡ \gcd gcd 并降枚举上限。

  2. μ \mu μ 套互质,然后瞬间和中间两个 ∑ \sum 说再见。

  3. 再换元,更改循环顺序,用奇特的性质再消掉一个 ∑ \sum

  4. 观察模拟。

本题到了 ∑ T = 1 n ϕ ( T ) ⌊ n T ⌋ 2 \sum_{T=1}^n \phi(T) \lfloor \frac{n}{T} \rfloor^2 T=1nϕ(T)Tn2 这步,直接枚举即可通过了。

时间复杂度: O ( n ) O(n) O(n). 实际得分: 100 p t s 100pts 100pts.

算法四

考虑推式子之后一个加强。

题意基本不变,改范围为:

T T T 组询问 n n n 的答案, T ≤ 5 × 1 0 5 T \leq 5 \times 10^5 T5×105 n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105.

显然 O ( n × T ) O(n \times T) O(n×T) 过不了。

考虑 ⌊ n T ⌋ 2 \lfloor \frac{n}{T} \rfloor^2 Tn2 可以整除分块,然后 ϕ \phi ϕ 可以预处理做前缀和。

这样可以单组询问 O ( n ) O(\sqrt{n}) O(n ),预处理 O ( n ) O(n) O(n).

此处应当有掌声!

时间复杂度: O ( n + T n ) O(n + T \sqrt{n}) O(n+Tn ).

实际得分: 100 p t s 100pts 100pts.

#pragma GCC optimize(2)
#include
using namespace std;

typedef long long ll;
const int N=1e5+1;

inline int read(){char ch=getchar(); int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

ll s[N]; bool h[N];
int phi[N],prime[N],cnt=0;
int n; ll ans=0;

inline void Euler(int n) {
	phi[1]=1; s[1]=1;
	for(int i=2;i<=n;i++) {
		if(!h[i]) phi[i]=i-1,prime[++cnt]=i;
		for(int j=1;j<=cnt && prime[j]*i<=n;j++) {
			h[i*prime[j]]=1;
			if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j]; break;}
			phi[prime[j]*i]=phi[i]*(prime[j]-1);
		} s[i]=s[i-1]+phi[i];
	}
} //预处理 phi 和 s (s 为前缀和)

int main() {
	Euler(N-1); n=read();
	for(int i=1;i<=n;) {
		ll t=n/(n/i);
		ans+=1ll*(n/t)*(n/t)*(s[t]-s[i-1]);
		i=t+1; //整除分块板子
	} printf("%lld\n",ans);
	return 0;
}

附:本题没有取模,这是令人惊讶的一点——一般的大式子都要取模的。为什么呢?

因为, n = 1 0 5 n=10^5 n=105 时答案才 72434344904 72434344904 72434344904,而答案总不会超过 n 3 n^3 n3 (每个 gcd ⁡ ≤ n \gcd \leq n gcdn 哇,非常粗略的估算)。

n = 1 0 6 → a n s = 8643257847824 n=10^6 \rightarrow ans = 8643257847824 n=106ans=8643257847824.

n = 1 0 7 → a n s = 1004297420038032 n=10^7 \rightarrow ans = 1004297420038032 n=107ans=1004297420038032.

看到了吧,我们的估计大的多了, long long \text{long long} long long 没有问题的。

你可能感兴趣的:(数论,#素数,莫比乌斯)