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
则原式可化为
$sumlimits_{p=1}^{n} sumlimits_{i=1}^{n} sumlimits_{j=1}^{n} varphi(p)[gcd(i,j)=p]$
由于$varphi(p)$和i、j均无关,故我们可以把$varphi(p)$提到前面化为
$sumlimits_{p=1}^{n} varphi(p)sumlimits_{i=1}^{n} sumlimits_{j=1}^{n}[gcd(i,j)=p]$
考虑把$[gcd(i,j)=p]$ 中的p除到前面,则原式可化为
$sumlimits_{p=1}^{n} varphi(p)sumlimits_{i=1}^{lfloor n/p floor} sumlimits_{j=1}^{lfloor n/p floor}[gcd(i,j)=1]$
这时我们设一个函数f(n) 表示 $sumlimits_{i=1}^{n}sumlimits_{j=1}^{n}[gcd(i,j)=1]$
则原式可视为 $sumlimits_{p=1}^{n} varphi(p)*f(lfloor n/p floor)$
考虑化简函数f(n) 。我们发现这个函数和欧拉函数相当相像。首先我们只考虑$ige j$的情况;
  即为
$sumlimits_{i=1}^{n} sumlimits_{j=1}^{i}[gcd(i,j)=1] => sumlimits_{i=1}^{n}varphi(i)$
  同理,我们发现只考虑$ile j$的情况,
也为$sumlimits_{j=1}^{n}varphi(j)$
两种情况同时考虑,发现有一种$i=j$的情况重复,故$f(n)$最终可化简为$2 imessumlimits_{i=1}^{n}varphi(i) -1$
综上所述,我们可以发现$ans = sumlimits_{p=1}^{n} varphi(p) imes ( (2 imessumlimits_{i=1}^{n}varphi(i)) -1)$
现在我们有了一个$O(2 imes n)$的方法求解$ans$ 但是数据组数为$T = 5000$ .
我们需要更加优化的方法才能解决问题,
刚刚的2 * n中有一个n是预处理出$varphi()$以及其前缀和。
是不和T相乘的,故我们考虑把每个答案$O(sqrt n)$处理
发现在处理$s[lfloor n/p floor]$中,$lfloor n/p floor$在每一段的值都是相同的,共有约$sqrt n$段,
而根据乘法结合律,我们可以先把$varphi()$加到一起,
再来乘这个相等的s。s是在预处理中做好的,
故总的时间复杂度为$O(2 imes n + T imes 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
",ans);
    }
    
}

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

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

原文地址:https://www.cnblogs.com/Tobichi/p/9168636.html