洛谷 P1891 疯狂LCM 题解

原题链接

享受推式子的乐趣吧

数论真有趣!

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

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

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

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

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

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

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

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

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

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

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

时间复杂度: \(O(n \times 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",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 \times (\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\).

posted @ 2020-03-17 16:45  bifanwen  阅读(124)  评论(0)    收藏  举报