前置技能:
1.反素数,推荐博客:ACdreamers讲解反素数。
定义:对于任何正整数 n ,其约数的个数记做 f(n) .例如 f(1) = 1, f(6) = 4 .如果某个正整数n满足:对于任意 i ( 0 < i < n ) ,都有 f( i ) < f( n ),则称 n 为反素数.
反素数的应用:
(1). 给定一个数 x,求一个最小的正整数 n,使得 n 的约数个数为 x
(2). 求出 1~n 中约数个数最多的数
2.算数基本定理,又称唯一分解定理。
内容:任何一个大于1的自然数 N,都可以唯一分解成有限个质数的乘积 N=p1^(a1)*p2^(a2)…pn^(an),这里p1<p2<……<pn均为质数,其诸指数ai是正整数。它的正因数个数为f(N)=(1+a1)(1+a2)…(1+an)。
其实网上的讲解已经很多了,我主要是给每个题解都加了适当的注释,希望刚开始学习反素数的朋友可能看懂。
思路:
以下问题的思路都是差不多的,所以先统一来说一下大体思路。根据算数基本定理我们可以知道,每个自然数都可以唯一写为素数幂的乘积的形式。反之,我们可以通过素数幂的乘积得到每个自然数,我们可以通过深搜实现。当数据最大为 10^18 时,只需要前16个素数就够了,得到的深搜树最高的时候是60个2的乘积,树高为60。
所以,我们可以通过深搜得到因子个数为 x 的最小数 n ,也可以通过深搜得到 1~n 中因子数最多数的最小数。
需要注意的是,我们要进行适当的剪枝,只有16个素数,如果要取第17个素数的时候显然是非法的,如果当前数的因子数比要求的 x 大的话显然也不必继续搜索了,如果当前数的数值比给定的 n 还要大的话也显然没必须继续搜索。
至于具体实现一两句话也说不明白,还是看代码和注释吧。
题目1.codeforces Number With The Given Amount Of Divisors
题意:给定一个数 x,求一个最小的正整数 n,使得 n 的约数个数为 x。
代码:
#include<stdio.h>
#include<string.h>
typedef long long LL;
LL ans;
int n,p[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
void dfs(int pos,int num,LL tmp)
{ //第pos个素数,当前值的因子数,当前值
int i;
if(num>n||pos>15) return; //因子个数>n或要取第17个素数则直接返回
if(num==n&&ans>tmp)
{ //因子数为n且数值更小,则更新答案
ans=tmp;
return;
}
for(i=1;i<=63;i++)
{ //表示取i个第pos个素数
if(tmp>ans/p[pos]) break; //如果下一个值大于ans则不考虑
tmp*=p[pos];
//根据算数基本定理,因子数为num*(i+1)
if(n%(num*(i+1))==0) dfs(pos+1,num*(i+1),tmp);
}
}
int main()
{
while(~scanf("%d",&n))
{
ans=(LL)1<<62;
dfs(0,1,1);
printf("%lld\n",ans);
}
return 0;
}
题目2.bzoj 1053 反素数ant
代码:
#include<stdio.h>
#include<string.h>
typedef long long LL;
LL ans;
int n,max,p[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
void dfs(int pos,int num,LL tmp)
{ //第pos个素数;当前值的因子数;当前值
int i;
if(pos>15) return; //如果搜索到第17个素数则停止
if(num>max)
{ //如果当前值因子个数更多则更新
max=num;
ans=tmp;
}
//如果当前值因子个数和最大因子数相等且当前值更小则更新
else if(num==max&&ans>tmp) ans=tmp;
for(i=1;i<=63;i++)
{ //取i个第pos个素数
if(n/p[pos]<tmp) break; //如果下一个值比n大则不考虑
tmp*=p[pos];
//根据算数基本定理,因子数为num*(i+1)
dfs(pos+1,num*(i+1),tmp);
}
}
int main()
{
while(~scanf("%d",&n))
{
ans=(LL)1<<62;
max=0;
dfs(0,1,1);
printf("%lld\n",ans);
}
return 0;
}
题目3.51nod 1060 最复杂的数
题意:求出 1~n 中约数个数最多的最小数,该题和第二题不同,n的取值范围为 1<= n <= 10^18,必须剪枝。具体做法就是限制当前选择素数的个数不能超过之前选择的素数个数。
代码:
#include<stdio.h>
#include<string.h>
typedef long long LL;
LL n,ans;
int max,p[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
void dfs(int pos,int limit,int num,LL tmp)
{ //第pos个素数;上一个因子个数;当前值的因子个数;当前值
int i;
if(pos>15) return; //如果搜索到第17个素数则停止
if(num>max)
{ //如果当前值因子个数更多则更新
max=num;
ans=tmp;
}
//如果当前值因子个数和最大因子数相等且当前值更小则更新
else if(num==max&&ans>tmp) ans=tmp;
//限制当前选择素数的个数不能超过之前选择的素数个数
for(i=1;i<=limit;i++)
{ //取i个第pos个素数
if(n/p[pos]<tmp) break; //如果下一个值比n大则不考虑
tmp*=p[pos];
//根据算数基本定理,因子数为num*(i+1)
dfs(pos+1,i,num*(i+1),tmp);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lld",&n);
ans=(LL)1<<62;
max=0;
dfs(0,60,1,1);
printf("%lld %d\n",ans,max);
}
return 0;
}