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("");
}
}
数论题果然有点烧脑呀!
![](https://i-blog.csdnimg.cn/blog_migrate/768939f67c731574e1b7840bc94127bb.jpeg#pic_center)