数论 —— 约数

【唯一分解定理】

对于任意一个大于 1 的正整数 n,都有且只有一种方式写出其素因子的乘积表达式。

即:n=(p_1^{a_1})*(p_2^{a_2})*...*(p_n^{a_k}),a_i \in Z,其中,P_i 均为素数

因此,当给出一个数 n 时,可以暴力枚举,以获取 n 的素因子

map<int, int> num;//记录素因子个数
int factor[N],cntF;//记录素因子
void getFactor(int n){
    cntF = 0;
    num.clear();
    for (int i = 2; i <= n / i;i++){
        if(n%i == 0)//记录素因子
            factor[cntF++] = i;
        while(n%i == 0) {
            n /= i;
            num[i]++;//记录素因子个数
        }
    }
    if(n > 1) {
        factor[cntF++] = n;
        num[n]++;
    }
}
int main(){
    int n;
    scanf("%d",&n);
    getFactor(n);
    for(int i=0;i<cntF;i++)
        cout<<factor[i]<<":"<<num[factor[i]]<<endl;
    return 0;
}

【约数个数函数】

对于一个整数 n,将其通过唯一分解定理分解,有: n=(p_1^{a_1})*(p_2^{a_2})*...*(p_n^{a_k}),a_i \in Z

那么,n 的因子个数为:d(n)=(1+a_1)*(1+a_2)*....*(1+a_n),a_i \in Z

显然,d(n) 是一个积性函数,那么可以用线性筛求出

由于每个数由其最小质因子筛出,因此要开一个辅助数组 num[i] 来表示 i 最小因子的次幂

那么,可分为以下三种情况讨论

1)i 是质数

当 i 为质数时,其约数仅有 1 与其自身,显然存在:d[i]=2,num[i]=1

2)i%prime[j]!=0

当 i%prime[j]!=0 时,i 没有 prime[j] 这个质因子,然而 i*prime[j] 中包含 prime[j]

根据前面得到的 d[i]=(a1+1)(a2+1)...(ak+1)

因此,要加上当前的 prime[j],从而有:d[i*prime[j]]=(a1+1)(a2+1)...(ak+1)(1+1)

即:d[i*prime[j]]=d[i]*d[prime[j]]=d[i]*2

由于当前的 prime[j] 必然是 i*prime[j] 的最小素因子,因此当前最小素因子的个数为:num[i*prime[j]]=1

3)i%prime[j]=0

当 i%prime[j]=0 时,i 中必然包含了至少一个 prime[j],且 prime[j] 必定是 i 的最小质因数

由于 i∗prime[j] 与 i 相比,至少多了一个最小质因子,根据前面得到的 d[i]=(a1+1)(a2+1)...(ak+1)

那么有:d[i∗prime[j]]=(a1+1+1)(a2+1)...(ak+1)

即:d[i∗prime[j]]=d[i]/(a1+1)∗(a1+2)

当前最小素因子的个数也在原来的 num[i] 的基础多了一个,即:num[i∗prime[j]]=num[i]+1

int d[N],num[N];
int prime[N],cnt;
bool bprime[N];
void getFactorNum(int n){
    d[1]=1;
    num[1]=1;
    for(int i=2;i<=n;i++){
        if(!bprime[i]){
            prime[++cnt]=i;
            d[i]=2;
            num[i]=1;
        }
        for(int j=1;j<=cnt&&i*prime[i]<=n;j++){
            bprime[i*prime[j]]=true;
            if(i%prime[j]!=0){
                d[i*prime[j]]=2*d[i];
                num[i*prime[j]]=1;
            }
            else{
                d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);
                num[i*prime[j]]=num[i]+1;
                break;
            }
        }
    }
}
int main(){
    int n;
    scanf("%d",&n);
    getFactorNum(n);
    for(int i=1;i<=n;i++)
        printf("%d\n",d[i]);
    return 0;
}

【约数和函数】

对于一个整数 n,将其通过唯一分解定理分解,有: n=(p_1^{a_1})*(p_2^{a_2})*...*(p_n^{a_k}),a_i \in Z

其所有因子之和为:\sigma (n)=(1+p_1+p_1^{2}+...+p_1^{a_1})*(1+p_2+p_2^{2}+...+p_2^{a_2})*...*(1+p_n+p_n^{2}+...+p_n^{a_k})

显然,σ(n) 是一个积性函数,那么可以用线性筛求出

设 sp[i] 表示 i 的最小质因子的各次幂之和,即:

sp(i)=(1+p_1+p_1^{2}+...+p_1^{a_1})

那么,可分为以下三种情况讨论:

1)i 是质数时

当 i 为质数时,其约数仅有 1 与其自身,显然存在:sd[i]=i+1,sp[i]=i+1

2)i%prime[j]!=0

当 i%prime[j]!=0 时,i 中不包含 prime[j] 这个素因子,且 prime[j] 一定是 i*prime[j] 的最小素因子

那么由: sd(i)=(1+p_1+p_1^{2}+...+p_1^{a_1})*(1+p_2+p_2^{2}+...+p_2^{a_2})*...*(1+p_n+p_n^{2}+...+p_n^{a_i})

得:sd(i*prime[j])=(1+p_1+p_1^{2}+...+p_1^{a_1})*(1+p_2+p_2^{2}+...+p_2^{a_2})*...*(1+p_n+p_n^{2}+...+p_n^{a_i})*(1+prime[j])

即有:sd[i*prime[j])=sd[i]*sd[prime[j]]

相应的,sp[i*prime[j]]=sp[prime[j]]=prime[j]+1

3)i%prime[j]=0

当 i%prime[j]=0 时,i 中至少包含了一个 prime[j]

那么由: sd(i)=(1+p_1+p_1^{2}+...+p_1^{a_1})*(1+p_2+p_2^{2}+...+p_2^{a_2})*...*(1+p_n+p_n^{2}+...+p_n^{a_i})

得:sd(i*prime[j])=(1+p_1+p_1^{2}+...+p_1^{a_1}+p_1^{a_1+1})*(1+p_2+p_2^{2}+...+p_2^{a_2})*...*(1+p_n+p_n^{2}+...+p_n^{a_i})

可以发现,两式唯一的不同在于:\left\{\begin{matrix}1+p_1+p_1^{2}+...+p_1^{a_1} \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:(1)\\1+p_1+p_1^{2}+...+p_1^{a_1}+p_1^{a_1+1}\:\:\:\:\:\:\:\:\:\:\(2) \end{matrix}\right.

那么:sd[i*prime[j]=sd[i] / (1) * (2)

即:sd[i*prime[j]=sd[i]/sp[i]*(sp[i]*prime[j]+1)

相应的,sp[i*prime[j]]=sp[i]*prime[j]+1

int prime[N],cnt;
bool bprime[N];
int sd[N],sp[N];
void getSd(int n){
    sd[1]=1;
    sp[1]=1;

    for(int i=2;i<=n;i++){
        if(!bprime[i]){
            prime[++cnt]=i;

            sd[i]=i+1;
            sp[i]=i+1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<N;j++){
            int temp=prime[j]*i;
            bprime[temp]=true;
            if(i%prime[j]==0){
                sp[temp]=sp[i]*prime[j]+1;
                sd[temp]=sd[i]/sp[i]*sp[temp];
                break;
            }
            sd[temp]=sd[i]*sd[prime[j]];
            sp[temp]=prime[j]+1;
        }
    }
}
int main(){
    int n;
    scanf("%d",&n);
    getSd(n);
    for(int i=1;i<=n;i++)
        printf("%d\n",d[i]);
}

【最高次幂】

对于一个整数 n,若要求满足 n=a^p 这个式子最大的 p,其中 n 可能是负数,a、p 是整数

则可知:p = GCD(k1,k2,k3,...,kn)

比如:24 = 2^3*3^1,p 应该是 GCD(3, 1) = 1,即:24 = 24^1

          324 = 3^4*2^2,p应该是 GCD(4, 2) = 2,即:324 = 18^2

int prime[N],cnt;
bool bprime[N];
void make_prime(){
    memset(bprime,false,sizeof(bprime));

    for(int i=2;i<=N;i++){
        if(!bprime[i]){
            prime[cnt++]=i;
            for(LL j=i*2;j<=N;j+=i)
                bprime[j]=true;
        }
    }
}
int GCD(int a, int b){
    return a % b == 0 ? b : GCD(b, a % b);
}

int main(){
    makePrime();
    int n;
    scanf("%d",&n);

    bool flag=false;
    if(n<0){//负数的情况
        n=-;
        flag=true;
    }

    int res=0;
    for(int i=0;i<cnt&&prime[i]<=n;i++){
        if(n%prime[i]==0){//枚举每一组的prime^k
            int k=0;
            while(n%prime[i]==0){
                k++;
                n/=prime[i];
            }
            //求p=GCD(k1,k2,...,kn)
            if(res==0)
                res=k;
            else
                res=GCD(res,k);
        }
    }
    
    if(n>1)//若质数除不尽
        res=GCD(res,1);
    
    if(flag){//负数时,幂不可能为偶数
        while(res%2==0)
            res/=2;
    }

    printf("%d\n",res);
    return 0;
}

【例题】

  1. Mysterious Bacteria(LightOJ-1220)(最高次幂):点击这里
  2. Factors of Factorial(AtCoder-2286)(约数个数):点击这里
  3. Product of Three Numbers(CF-1294C)(约数个数):点击这里
  4. Aladdin and the Flying Carpet(LightOJ-1341)(枚举约数):点击这里
  5. 炫酷数字(2019牛客寒假算法基础集训营 Day5-G)(约数+构造):点击这里
  6. Sigma Function(LightOJ-1336)(约数和+公式推导):点击这里
posted @   老程序员111  阅读(140)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示