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 ,满足Pd Pn 的子集,这样的 d 是比较容易求的,只需要求出n的质因数分解后用线性筛就可以处理,但是本题可以直接暴力一点预处理。

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


代码:

#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);
}

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


代码:

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值