NKOJ 3800 分解质因数(欧拉函数+线性筛)

P3801分解质因数

问题描述

记Pi表示正整数i的质因数集合。

已知正整数n,求满足下列条件的有序正整数对(a,b)的数目:

(1)1<=a<=b<=n
(2)t为a,b的最大公约数,Pt是Pn的子集

输入格式

一个正整数n.

输出格式

一个正整数,表示合题意的有序正整数对的数目.

样例输入 1

6

样例输出 1

20

样例输入 2

7

样例输出 2

19

数据范围

50%的数据1<=n<=2000;

100%的数据1<=n<=1000000.


此题有两种做法。

做法一:先介绍一种比较简单粗暴的。
我们考虑一个数$d$,满足$P_{d}$是$P_{n}$的子集,这样的$d$是比较容易求的,只需要求出$n$的质因数分解后用线性筛就可以处理,但是本题可以直接暴力一点预处理。

总之,我们求出了$d$,那么现在需要考虑对于每一个$d$,有多少个数对$(A,B)$满足$gcd(A,B)=d$,那么根据$gcd$的性质,有$gcd(\frac {A}{d},\frac {B}{d})=1$
那么显然我们只需要找出在$1-\frac {n}{d}$的范围中,有多少对互质的整数即可,那么显然是$\sum_{i=1}^{\frac{n}{d}} \varphi(i)$,那么$\varphi$的前缀和$S_{\varphi}$可以用线性筛处理,然后枚举$d$把$S_{\varphi}(\frac{n}{d})$累加起来就是答案。


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<iostream>
#include<algorithm>
#define N 1000005
#define ll long long
using namespace std;
ll n,phi[N],S[N],P[N],tot,ans;
bool mark[N];
int main()
{
ll i,j;
scanf("%lld",&n);phi[1]=1;S[1]=1;
for(i=2;i<=n;i++)
{
if(!phi[i])phi[i]=i-1,P[++tot]=i;
for(j=1;j<=tot&&P[j]*i<=n;j++)
if(i%P[j])phi[i*P[j]]=phi[i]*(P[j]-1);
else {phi[i*P[j]]=phi[i]*P[j];break;}
S[i]=S[i-1]+phi[i];
}
for(i=1;i<=tot;i++)if(n%P[i])for(j=P[i];j<=n;j+=P[i])mark[j]=1;
for(i=1;i<=n;i++)if(!mark[i])ans+=S[n/i];
printf("%lld",ans);
}

做法二:
做法二比较的巧妙,我们可以认为在$P_{n}$中的质数不是质数,那么题设条件就变成了求有多少对互质的数对,那么显然是欧拉函数求解,但是由于质数是改动过的,我们记他为$\varphi’(x)$,那么因为这个函数与欧拉函数基本是一样的,那么他显然是积性函数,那么就可以用线性筛来搞。
具体来说,我们在普通的线性筛中,遇到质数时判断一下,如果这个质数在$P_{n}$中,那么$\varphi’(x)=x$,否则$\varphi’(x)=x-1$,然后合数还是通过积性函数性质来搞。


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h> 
#include<iostream>
#include<cmath>
using namespace std;
const int N=20000050;
int n,phi[N],prime[N];
bool mark[N],p[N];
void linear()
{
int i,j,tot=0; phi[1]=1;
for(i=2;i<=n;i++)
{
if(!phi[i])
{
if(!p[i])phi[i]=i-1;
else phi[i]=i;
prime[++tot]=i;
}
for(j=1;j<=tot&&prime[j]*i<=n;j++)
if(i%prime[j]==0){phi[i*prime[j]]=phi[i]*prime[j];break;}
else phi[i*prime[j]]=phi[i]*(phi[prime[j]]);
}
}
main()
{
int i,j,k;
cin>>n;
int tmp=n;
for(i=2;i*i<=n&&tmp!=1;i++)
if(tmp%i==0)
{
while(tmp%i==0)tmp/=i;
p[i]=true;
}
if(tmp!=1)p[tmp]=true;
linear();
long long ans=0;
for(i=1;i<=n;i++)ans+=1LL*(phi[i]);
cout<<ans;
}