洛谷 P1891 疯狂LCM 题解

原题链接

享受推式子的乐趣吧

数论真有趣!

庆祝:数论紫题第 (3) 道。

[sum_{i=1}^n operatorname{lcm}(i,n) ]

[= sum_{i=1}^n frac{i imes n}{gcd(i,n)} ]

[= n imes sum_{i=1}^n frac{i}{gcd(i,n)} ]

[= n imes sum_{d|n} sum_{i=1}^n frac{i}{d} [gcd(i,n) == d] ]

[= n imes sum_{d|n} sum_{i=1}^{frac{n}{d}} i [gcd(i,frac{n}{d}) == 1] ]

[= n imes sum_{d|n} sum_{i=1}^d i [gcd(i,d)==1] ]

(注:由于 (d) 是枚举因数,因数成对出现,所以 (frac{n}{d}) 等同于 (d)).

[= n imes sum_{d|n} frac{d}{2} phi_d ]

感觉,数论上大部分是 gcd,然后就是欧拉筛

欧拉筛 日常一下,然后可以提前计算答案。(类似于打表?)

时间复杂度: (O(n imes d)). ((d) 的值之后解释)

空间复杂度: (O(n)).

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N1=1e6+1;
const int N=1e6;

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

int T; ll ans[N1];
ll n,f=0;
ll phi[N1],prime[N1];

inline void Euler() {
    phi[1]=1;
    for(int i=2;i<=N;i++) {
        if(!phi[i]) prime[++f]=i,phi[i]=i-1;
        for(int j=1;j<=f && i*prime[j]<=N;j++) {
            if(i%prime[j]==0) {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    } 
} //欧拉筛模板

int main(){
    T=read(); Euler();
    for(int i=1;i<=N;i++)
    for(int j=i;j<=N;j+=i) ans[j]+=phi[j/i]*(j/i)+1>>1;
    while(T--) {
    	n=read();
    	printf("%lld
",n*ans[n]);
	}
	return 0;
}

下面分析一下时间复杂度。

你会发现,除了这一段:

for(int i=1;i<=N;i++)
for(int j=i;j<=N;j+=i) ans[j]+=phi[j/i]*(j/i)+1>>1;

较难计算时间,其余都是 (O(n)).

那么这一段的时间,我们再来推个式子:

[= sum_{i=1}^n frac{n}{i} ]

[= n imes (sum_{i=1}^n frac{1}{i}) ]

这时,你可能想到了 欧拉调和级数 ,但这里 (n) 是有限的。

式子推不下去,我们就打了个暴力。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

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;}

int main(){
    double x=0;
    for(int i=1;i<=1000000;i++) x+=1.0/i;
    cout<<x;
    return 0;
}

最终结果: (14.3927)

那么,这可以认为是较小的一个常数(因为它不会影响程序通过本题)。

所以,最终的时间复杂度为: (O(n)).

实际得分: (100pts).

原文地址:https://www.cnblogs.com/bifanwen/p/12511670.html