51Nod 1239 欧拉函数之和

转载请注明出处,谢谢http://blog.csdn.net/bigtiao097?viewmode=contents

题意

s(n)=i=1nφ(i)

φ(n) 是欧拉函数
给定n,求 s(n)mod109+7  (2n1010)

思路

这是我学杜教筛做的第二个题,第一个就是和这个非常相似的求莫比乌斯函数之和
给大家推荐唐老师的一篇文章,个人感觉讲的非常好,我就是在这里学的
欧拉函数的一个性质是这样的

n=d|nφ(d)

所以就有
n×(n+1)2=i=1ni=i=1nd|iφ(d)=i=1nd=1niφ(d)=i=1ns(ni)(1)(2)

这样就得到了
s(n)=n×(n+1)2i=2ns(ni)

感觉上面式子中最难理解的地方就是 (1)(2) 的过程了,这里稍微解释一下:
可以这样理解

i=1nd|iφ(d)=i=1nd|iφ(id)=i=1nj=1niφ(j)

第二个式子中的 i  理解为枚举每一个数,最后一个式子中的 i  理解为枚举每一个因子

对于

s(n)=n×(n+1)2i=2ns(ni)

然后就可以递归求解了,顺便加记忆化,顺便加上分段优化来求了
关于复杂度的问题,具体可以看上面推荐的文章,这里稍微说一下

  • 如果我们先提前筛选出了 n23 φ(n) 的前缀和,总体复杂度大概是 O(n23)
  • 如果没有提前处理一下,直接递归求解的话, 总体复杂度大概是 O(n34)

然后再说一下递归过程中记忆化的时候用的什么数据结构
可以看一下我的另一篇文章里面说的传送门
这个题用三种结构都能过,而且时间差不多


具体代码如下:
Result:Accepted     Memory:26108 KB     Time :1406 ms

#include<bits/stdc++.h>
const int maxn = 4641590;//maxn = n^(2/3)
const int mod = 1e9+7;
using namespace std;
typedef long long ll;
const int HASH_MOD=987654;
int phi[maxn];
bool vis[maxn];
int p[maxn];
ll n;
unordered_map<ll,ll> mp;
void euler_init()
{
    phi[1]=1;
    for(int i=2;i<maxn;i++)
    {
        if(!vis[i])
        {
            phi[i]=i-1;
            p[++p[0]]=i;
        }
        for(int j=1;p[j]*i<maxn;j++)
        {
            vis[p[j]*i]=1;
            if(i%p[j]==0)
            {
                phi[p[j]*i]=phi[i]*p[j]%mod;
                break;
            }
            phi[p[j]*i]=phi[i]*(p[j]-1)%mod;
        }
    }
    for(int i=1;i<maxn;i++)
        phi[i]= (phi[i]+phi[i-1])%mod;
}
ll mod_mul(ll x,ll n)
{
    ll res = 0;
    while(n>0)
    {
        if(n&1) res = (res+x)%mod;
        x = (x<<1)%mod;
        n>>=1;
    }
    return res;
}
ll calc(ll x)
{
    unordered_map<ll,ll>::iterator it;
    if (x<maxn) return phi[x];
    if((it=mp.find(x))!= mp.end())
        return it->second;
    ll ans = 0;
    if(x&1)
        ans = mod_mul(x,(x+1)/2);
    else
        ans = mod_mul(x/2,x+1);
    for(ll l=2,r; l<=x; l=r+1)
    {
        r=x/(x/l);
        ans=(ans-calc(x/l)*((r-l+1)%mod)%mod+mod)%mod;
    }
    return mp[x] = ans;
}
int main()
{
    euler_init();
    scanf("%lld",&n);
    printf("%lld",calc(n));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值