• 洛谷 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).

  • 相关阅读:
    基于Token的WEB后台认证机制
    MySQL主键设计
    .NET学习笔记 -- 那堆名词到底是啥(CLR、CLI、CTS、CLS、IL、JIT)
    C#学习笔记 -- Attribute
    Log4Net指南
    Windows进程通信 -- 共享内存(1)
    windows进程通信 -- WM_COPYDATA消息
    C语言再学习之内存对齐
    找工作中……
    做了一个vue的同步tree 的npm组件包
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12511670.html
Copyright © 2020-2023  润新知