【51nod】欧拉函数之和(数论,杜教筛)

版权声明:本文为博主原创文章,不管你喜不喜欢都请在注明作者后转载~( ̄▽ ̄~) https://blog.csdn.net/C20190102/article/details/84787209

题目

1239 欧拉函数之和

分析

欧拉函数 φ ( n ) \varphi(n) 表示小于等于 n n 的与 n n 互质的数的个数。
令答案 f ( n ) = i = 1 n φ ( i ) f(n)=\sum\limits_{i=1}^{n}\varphi(i)

一个性质

可以证明 n = d n φ ( d ) n=\sum\limits_{d|n}\varphi(d)
下面是一个我理解的很不正经的证明:

列出以 n n 为分母的所有分数: 1 n , 2 n , . . . , n n \dfrac{1}{n},\dfrac{2}{n},...,\dfrac{n}{n}
可以理解 φ ( b ) \varphi(b) 表示的是以 b b 为分母的最简真分数的个数。
a b ( a < b ) \dfrac{a}{b}(a<b) 为最简分数,当且仅当 b n b|n ( a , b ) = 1 (a,b)=1
你仔细地想, d n φ ( d ) \sum\limits_{d|n}\varphi(d) 就表示出了 1 n , 2 n , . . . , n n \dfrac{1}{n},\dfrac{2}{n},...,\dfrac{n}{n} ,于是 n = d n φ ( d ) n=\sum\limits_{d|n}\varphi(d)

尝试递推

n = φ ( n ) + d n , d < n φ ( d ) \therefore n=\varphi(n)+\sum\limits_{d|n,d<n}\varphi(d)
φ ( n ) = n d n , d < n φ ( d ) \therefore\varphi(n)=n-\sum\limits_{d|n,d<n}\varphi(d)
f ( n ) = i = 1 n ( i d i , d < i φ ( d ) ) \therefore f(n)=\sum\limits_{i=1}^{n}\left(i-\sum\limits_{d|i,d<i}\varphi(d)\right)
f ( n ) = i = 1 n i i = 1 n d i , d < i φ ( d ) = n ( n + 1 ) 2 i = 1 n d i , d < i φ ( d ) \therefore f(n)=\sum\limits_{i=1}^{n}i-\sum\limits_{i=1}^{n}\sum\limits_{d|i,d<i}\varphi(d)=\dfrac{n(n+1)}{2}-\sum\limits_{i=1}^{n}\sum\limits_{d|i,d<i}\varphi(d)

i = 1 n d i , d < i φ ( d ) \sum\limits_{i=1}^{n}\sum\limits_{d|i,d<i}\varphi(d) ,算贡献,则可以等价为: d = 1 n i = 1 n d φ ( d ) = d = 1 n ( n d φ ( d ) ) \sum\limits_{d=1}^{n}\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\varphi(d)=\sum\limits_{d=1}^{n}\left(\lfloor\frac{n}{d}\rfloor·\varphi(d)\right) ,下面这个图可以帮助理解,但我无法言说:
d的贡献
(其中 k = n d k=\lfloor\frac{n}{d}\rfloor

但是这样还是不能递推,那就换一种方法,枚举 d d 系数 i i ,再枚举 d d 并统计 i d id 之前(由此可以得到枚举的 d d 必须满足 d n i d\leq\lfloor\frac{n}{i}\rfloor ,因为 i d n id\leq n )的 d d i = 1 n d = 1 n i φ ( d ) \sum\limits_{i=1}^{n}\sum\limits_{d=1}^{\lfloor\frac{n}{i}\rfloor}\varphi(d)

于是递推式有了: f ( n ) = n ( n + 1 ) 2 i = 1 n f ( n i ) f(n)=\dfrac{n(n+1)}{2}-\sum\limits_{i=1}^{n}f(\lfloor\frac{n}{i}\rfloor)

分块

直接记忆化搜索还是会超时,所以我们需要用分块(俗称数线段)。
发现对于某一区域 [ l , r ] [l,r] i i f ( n i ) f(\lfloor\frac{n}{i}\rfloor) 是相等的:
n = 10 n=10

i i 1 2 3 4 5 6 7 8 9 10
n i \left\lfloor\dfrac{n}{i}\right\rfloor 10 5 3 2 2 1 1 1 1 1

i [ 6 , 10 ] i\in[6,10] 时,所有的 f ( n i ) f(\lfloor\frac{n}{i}\rfloor) 都是 1 1 ,于是 i = 6 10 f ( n i ) = ( 10 6 + 1 ) × f ( n 6 ) = 5 f ( 1 ) \sum\limits_{i=6}^{10}f(\lfloor\frac{n}{i}\rfloor)=(10-6+1)\times f(\lfloor\frac{n}{6}\rfloor)=5\cdot f(1) ,节省了 4 4 次计算。


以下内容我不负责,因为我没有试过。
可能你会说,由于记忆化了,所以重复调用也没关系。
但是我的记忆化用的map,所以有 l o g x logx 的复杂度。
如果你可以用unordered_map或者你愿意手打vector的哈希,有可能不会超时。


继续。
找规律发现当 i [ l , n n l ] i\in \left[l,\lfloor\frac{n}{\lfloor\frac{n}{l}\rfloor}\rfloor\right] 时,所有 f ( n i ) = f ( n l ) f(\lfloor\frac{n}{i}\rfloor)=f(\lfloor\frac{n}{l}\rfloor) ,于是做完了。

打表

直接分块记忆化搜索还是会超时,所以要把 φ ( n ) , n 5 × 1 0 6 \varphi(n),n\leq 5\times10^6 线性筛筛出来并计算前缀和(打 n 5 × 1 0 6 n\leq 5\times10^6 时的表)。

线性筛欧拉函数

一个性质

n n 唯一分解: n = p 1 α 1 p 2 α 2 . . . p m α m n={p_1}^{\alpha_1}{p_2}^{\alpha_2}...{p_m}^{\alpha_m}
则: φ ( n ) = n ( 1 1 p 1 ) ( 1 1 p 2 ) . . . ( 1 1 p m ) = n p 1 1 p 1 p 2 1 p 2 . . . p m 1 p m \varphi(n)=n(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_m})=n\cdot\frac{p_1-1}{p_1}\cdot\frac{p_2-1}{p_2}\cdot...\cdot\frac{p_m-1}{p_m}
不正经的证明(信竞要的是玄学感觉):

根据容斥原理。
我们需要把小于 n n p i ( i k ) p_i(i\leq k) 的倍数个数全部减去,
(会减重,如 p 1 p 2 p_1p_2 这个数被减了 2 2 次)再把小于 n n p i p j ( i m , j m , i j ) p_ip_j(i\leq m,j\leq m,i\neq j) 的倍数全部加上,
(会加多)再把小于 n n p i p j p k ( i m , j m , k m , i j k ) p_ip_jp_k(i\leq m,j\leq m,k\leq m,i\neq j\neq k) 的倍数全部减去,
……
小于 n n k k 的倍数的个数是 n k \lfloor\frac{n}{k}\rfloor ,由于 p i pi 能整除 n n ,所以 n p x \dfrac{n}{\prod p_x} 一定是整数。
把式子中的 n n 乘进去: φ ( n ) = ( n n p 1 ) ( 1 1 p 2 ) . . . ( 1 1 p m ) \varphi(n)=(n-\frac{n}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_m})
想象一下把它全部展开,就是一个容斥。

线性筛

φ ( n ) = n p 1 1 p 1 p 2 1 p 2 . . . p m 1 p m \varphi(n)=n\cdot\frac{p_1-1}{p_1}\cdot\frac{p_2-1}{p_2}\cdot...\cdot\frac{p_m-1}{p_m}
首先phi[1]=1
枚举i>1,若此时的phi[i]还没有值,说明i是素数(往下看就知道为什么了)。
用筛素数的原理,当i是素数时,它的倍数都不是素数。
再枚举i的倍数j,更新phi[j](所以phi充当了筛素数时的isPrime数组)。
由于i肯定是j的一个质因子,所以phi[j]*=(i-1)/i,当然,如果此时是phi[j]第一次被更新,应该先把phi[j]赋为j

代码

#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;

#define LL long long
#define MAXT 5000000
#define MOD 1000000007
#define INV2 500000004
LL phiSum[MAXT+5];
map<LL,LL> M;

void GetPhi(int N){
    phiSum[1]=1;
    for(int i=2;i<=N;i++){
        if(!phiSum[i]){
            for(int j=i;j<=N;j+=i){
                if(!phiSum[j])
                    phiSum[j]=j;
                phiSum[j]=phiSum[j]/i*(i-1);
            }
        }
    }
    for(int i=1;i<=N;i++)
        phiSum[i]=(phiSum[i]+phiSum[i-1])%MOD;//计算前缀和
}
LL Solve(LL x){
    if(x<=MAXT)//打了表就直接返回
        return phiSum[x];
    if(M.count(x))//记忆化
        return M[x];
    LL ret=x%MOD*((x+1)%MOD)%MOD*INV2%MOD;
    for(LL l=2,r;l<=x;l=r+1)//数线段
        r=x/(x/l),ret=((ret-(r-l+1)%MOD*Solve(x/l)%MOD)%MOD+MOD)%MOD;
    return M[x]=ret;
}

int main(){
    GetPhi(MAXT);
    LL N;
    scanf("%lld",&N);
    printf("%lld",Solve(N));
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/84787209