版权声明:本文为博主原创文章,不管你喜不喜欢都请在注明作者后转载~( ̄▽ ̄~) https://blog.csdn.net/C20190102/article/details/84787209
题目
1239 欧拉函数之和
分析
欧拉函数
φ(n)表示小于等于
n的与
n互质的数的个数。
令答案
f(n)=i=1∑nφ(i)。
一个性质
可以证明
n=d∣n∑φ(d)。
下面是一个我理解的很不正经的证明:
列出以
n为分母的所有分数:
n1,n2,...,nn。
可以理解
φ(b)表示的是以
b为分母的最简真分数的个数。
ba(a<b)为最简分数,当且仅当
b∣n且
(a,b)=1。
你仔细地想,
d∣n∑φ(d)就表示出了
n1,n2,...,nn,于是
n=d∣n∑φ(d)。
尝试递推
∴n=φ(n)+d∣n,d<n∑φ(d)
∴φ(n)=n−d∣n,d<n∑φ(d)
∴f(n)=i=1∑n⎝⎛i−d∣i,d<i∑φ(d)⎠⎞
∴f(n)=i=1∑ni−i=1∑nd∣i,d<i∑φ(d)=2n(n+1)−i=1∑nd∣i,d<i∑φ(d)
i=1∑nd∣i,d<i∑φ(d),算贡献,则可以等价为:
d=1∑ni=1∑⌊dn⌋φ(d)=d=1∑n(⌊dn⌋⋅φ(d)),下面这个图可以帮助理解,但我无法言说:
(其中
k=⌊dn⌋)
但是这样还是不能递推,那就换一种方法,枚举
d的系数
i,再枚举
d并统计
id之前(由此可以得到枚举的
d必须满足
d≤⌊in⌋,因为
id≤n)的
d:
i=1∑nd=1∑⌊in⌋φ(d)
于是递推式有了:
f(n)=2n(n+1)−i=1∑nf(⌊in⌋)
分块
直接记忆化搜索还是会超时,所以我们需要用分块(俗称数线段)。
发现对于某一区域
[l,r]内
i的
f(⌊in⌋)是相等的:
(
n=10)
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
⌊in⌋ |
10 |
5 |
3 |
2 |
2 |
1 |
1 |
1 |
1 |
1 |
当
i∈[6,10]时,所有的
f(⌊in⌋)都是
1,于是
i=6∑10f(⌊in⌋)=(10−6+1)×f(⌊6n⌋)=5⋅f(1),节省了
4次计算。
以下内容我不负责,因为我没有试过。
可能你会说,由于记忆化了,所以重复调用也没关系。
但是我的记忆化用的map
,所以有
logx的复杂度。
如果你可以用unordered_map
或者你愿意手打vector
的哈希,有可能不会超时。
继续。
找规律发现当
i∈[l,⌊⌊ln⌋n⌋]时,所有
f(⌊in⌋)=f(⌊ln⌋),于是做完了。
打表
直接分块记忆化搜索还是会超时,所以要把
φ(n),n≤5×106线性筛筛出来并计算前缀和(打
n≤5×106时的表)。
线性筛欧拉函数
一个性质
将
n唯一分解:
n=p1α1p2α2...pmαm
则:
φ(n)=n(1−p11)(1−p21)...(1−pm1)=n⋅p1p1−1⋅p2p2−1⋅...⋅pmpm−1
不正经的证明(信竞要的是玄学感觉):
根据容斥原理。
我们需要把小于
n的
pi(i≤k)的倍数个数全部减去,
(会减重,如
p1p2这个数被减了
2次)再把小于
n的
pipj(i≤m,j≤m,i̸=j)的倍数全部加上,
(会加多)再把小于
n的
pipjpk(i≤m,j≤m,k≤m,i̸=j̸=k)的倍数全部减去,
……
小于
n的
k的倍数的个数是
⌊kn⌋,由于
pi能整除
n,所以
∏pxn一定是整数。
把式子中的
n乘进去:
φ(n)=(n−p1n)(1−p21)...(1−pm1)
想象一下把它全部展开,就是一个容斥。
线性筛
φ(n)=n⋅p1p1−1⋅p2p2−1⋅...⋅pmpm−1
首先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));
}