n的约数 (数论+dfs)

n的约数(唯一分解定理)

题目链接:https://ac.nowcoder.com/acm/problem/15428
题目来源:牛客网

Problem Description

t次询问,每次给你一个数n,求在[1,n]内约数个数最多的数的约数个数。

Input

第一行一个正整数t
之后t行,每行一个正整数n


Output

输出t行,每行一个整数,表示答案


Sample Input

5
13
9
1
13
16

Sample Output

6
4
1
6
6

Hint

对于100%的数据,t <= 500 , 1 <= n <= 1000000000000000000

Solution

  • 鲁迅曾说过 ,由数论的唯一分解定理(又叫算数基本定理)可知:每个大于1的自然数,要么本身就是质数,要么可以写为2个或以上的质数的积,而且这些质因子按大小排列之后,写法仅有一种方式。
    例如:
    6936= 2 3 2^3 23 x 3 x 17        1200= 2 4 2^4 24 x 3 x 5 2 5^2 52

  • 用符号概括起来就是:对于任意的a,都有
    a= p 1 e 1 p_1^{e_1} p1e1x p 2 e 2 p_2^{e_2} p2e2x…x p k e k p_k^{e_k} pkek*, p i p_i pi为素数且 p 1 p_1 p1< p 2 p_2 p2<…< p k p_k pk
    根据鲁迅说过的乘法原理 ,那么a的总的因子数就是( e 1 e_1 e1+1) * ( e 2 e_2 e2+1) * ( e 3 e_3 e3+1)…( e k e_k ek+1)

  • 那么我们可不可以对1~n进行遍历,每个数 i 都通过唯一分解定理进行拆分,然后再利用乘法原理将分解的素数的幂求积呢?观察数据范围,1 <= n <= 1000000000000000000,由于是 1 0 18 10^{18} 1018的数量级,光是从1~ 1 0 18 10^{18} 1018 for循环一遍都会超时了,所以这种方法行不通。

  • 由于 2 64 2^{64} 264> 1 0 18 10^{18} 1018,所以这题中素数因子的是一定不会超过64的。实际上,我们可以先列出一定量的素数因子,比如2,3,5,7,11,…,41,43,47这些,假设我的素数数组prime[16]= {0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47}。有了这个数组为基础后,我们就可以开始枚举各个素数的值(即枚举 e i e_i ei)是多少,我们可以通过dfs(深度优先搜索)就行枚举,注意乘积不能大于n。

  • 有一点可以优化的是,对于约数个数最多的数 a= p 1 e 1 p_1^{e_1} p1e1x p 2 e 2 p_2^{e_2} p2e2x…x p k e k p_k^{e_k} pkek p i p_i pi为素数且 p 1 p_1 p1< p 2 p_2 p2<…< p k p_k pk,一定会有 e 1 e_1 e1>= e 2 e_2 e2>= e 3 e_3 e3…>= e k e_k ek,即 e i e_i ei是非递增的。那么我们就可以通过枚举的 e i e_i ei去限制 e i + 1 e_{i+1} ei+1的枚举范围,减少枚举的范围,提高搜索效率。这是可以证明的。

  • 有了以上的思考,我们就可以由唯一分解定理+深度优先遍历这套组合拳,把这道数论题给AC了。

Code

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N=1e5+100;
const int mod=1e9+7;

int T;
ll n;
ll ans=0;
int prime[16]= {0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47}; //prime[1~15]为前15个素数

template<class T> void qr(T &x) //快读
{
    int f=0;
    x=0;
    char c=getchar();
    for(; !isdigit(c); c=getchar())
        f^=c=='-';
    for(; isdigit(c); c=getchar())
        x=(x<<3)+(x<<1)+(c^48);
    x=f?(-x):x;
}

template<class T> void qw(T x) //快写
{
    if(x>=10)
        qw(x/10);
    putchar(x%10+'0');
}

void dfs(ll x,int index,int lim,ll num)// x为目前的乘积,index为素数下标,lim为小素数对大素数的限制,num为目前的因子个数
{
    if(index>15) //数组只有15个素数
        return;
    ans=max(ans,num);

    for(int i=1; i<=lim; i++) //枚举素数的幂
    {
        if(n/prime[index]<x)break; //防止乘积超过n

        x*=(1LL*prime[index]);
        dfs(x,index+1,i,1LL*(i+1)*num);
    }
}


int main()
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
#endif
    qr(T);
    while(T--)
    {
        ans=0;
        qr(n);
        dfs(1LL,1,64,1); //因为2^64>1e18,所以lim最大为64
        qw(ans);
        puts("");
    }
}


数论题果然有点烧脑呀!
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__Wedream__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值