BZOJ 4804: 欧拉心算 欧拉函数

4804: 欧拉心算

>原题链接<

Description

 给出一个数字N

Input

第一行为一个正整数T,表示数据组数。
接下来T行为询问,每行包含一个正整数N。
T<=5000,N<=10^7

Output

按读入顺序输出答案。

Sample Input

1
10

Sample Output

136

思路:

考虑化简原式,设gcd(i,j) = p
则原式可化为
$\sum\limits_{p=1}^{n} \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n} \varphi(p)[gcd(i,j)=p]$
由于$\varphi(p)$和i、j均无关,故我们可以把$\varphi(p)$提到前面化为
$\sum\limits_{p=1}^{n} \varphi(p)\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n}[gcd(i,j)=p]$
考虑把$[gcd(i,j)=p]$ 中的p除到前面,则原式可化为
$\sum\limits_{p=1}^{n} \varphi(p)\sum\limits_{i=1}^{\lfloor n/p\rfloor} \sum\limits_{j=1}^{\lfloor n/p \rfloor}[gcd(i,j)=1]$
这时我们设一个函数f(n) 表示 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=1]$
则原式可视为 $\sum\limits_{p=1}^{n} \varphi(p)*f(\lfloor n/p\rfloor)$
考虑化简函数f(n) 。我们发现这个函数和欧拉函数相当相像。首先我们只考虑$i\ge j$的情况;
  即为
$\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i}[gcd(i,j)=1] => \sum\limits_{i=1}^{n}\varphi(i)$
  同理,我们发现只考虑$i\le j$的情况,
也为$\sum\limits_{j=1}^{n}\varphi(j)$
两种情况同时考虑,发现有一种$i=j$的情况重复,故$f(n)$最终可化简为$2 \times\sum\limits_{i=1}^{n}\varphi(i) -1$
综上所述,我们可以发现$ans = \sum\limits_{p=1}^{n} \varphi(p) \times ( (2 \times\sum\limits_{i=1}^{n}\varphi(i)) -1)$
现在我们有了一个$O(2\times n)$的方法求解$ans$ 但是数据组数为$T = 5000$ .
我们需要更加优化的方法才能解决问题,
刚刚的2 * n中有一个n是预处理出$\varphi()$以及其前缀和。
是不和T相乘的,故我们考虑把每个答案$O(\sqrt n)$处理
发现在处理$s[\lfloor n/p\rfloor]$中,$\lfloor n/p\rfloor$在每一段的值都是相同的,共有约$\sqrt n$段,
而根据乘法结合律,我们可以先把$\varphi()$加到一起,
再来乘这个相等的s。s是在预处理中做好的,
故总的时间复杂度为$O(2\times n + T\times \sqrt n)$
下面附上代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10000001;
int phi[N+10], pr[N+10], tot;
bool np[N+10];
long long s[N+10];
void getphi() {
    int i, j;
    phi[1] = 1;
    for(i=2;i<=N;i++) {
        if(!np[i]) {
            pr[++tot] = i;
            phi[i] = i-1;
        }
        for(j=1;j<=tot&&i*pr[j]<=N;j++) {
            np[i*pr[j]]=1;
            if(i%pr[j] == 0) {
                phi[i*pr[j]]=phi[i]*pr[j];
                break;
            }
            else
                phi[i*pr[j]]=phi[i]*(pr[j] - 1);
        }
    }
    for(i=1;i<=N;i++) {
        s[i]=s[i-1]+phi[i];
    }
}
int main() {
    int t;
    scanf("%d",&t);
    getphi();
    while(t--) {
        int n;
        long long ans=0;
        scanf("%d",&n);
        int lst =0;
        for(int p=1;p<=n;p=lst+1) {
            lst = n/(n/p);
            ans+=(s[lst]-s[p-1])*(s[n/p]*2-1);
        }
        printf("%lld\n",ans);
    }
    
}

 欢迎来原博客看看>原文链接<

 特别感谢同届神犇@fcwww 给我的帮助

转载于:https://www.cnblogs.com/Tobichi/p/9168636.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值