文章已更新。
感谢 @stratoes 指出此文的错误,另外hdu2521数据存在一些问题。详见题型第三类。
再次感谢!
——————————————————————
做了POJ2286之后,发现题目要求求出1-n因数最多的那个数。一时做题做傻了(orz)没清晰的想到合适的方法,后来想到了是反素数。
顺便总结了常见的和反素数有关的题型(个人觉得反素数只是作为题目的某一部分出现),还请各位dalao轻拍。
其实顾名思义,素数就是因子只有两个的数,那么反素数,就是因子最多的数(并且因子个数相同的时候值最小),所以反素数是相对于一个集合来说的。
我所理解的反素数定义就是,在一个集合中,因素最多并且值最小的数,就是反素数。
那么,如何来求解反素数呢?
首先,既然要求因子数,我首先想到的就是素因子分解。把n分解成
的形式,其中p是素数,k为他的指数。这样的话
就是总因子个数。
但是显然质因子分解的复杂度是很高的,并且前一个数的结果不能被后面利用。所以要换个方法。
我们来观察一下反素数的特点。反素数肯定是从2开始的连续素数的幂次形式的乘积。
数值小的素数的幂次大于等于数值大的素数,即
中,有
解释:第一条:如果不是从2开始的连续素数,那么如果幂次不变,把素数变成数值更小的素数,那么此时因子个数不变,但是n的
数值变小了。交换到从2开始的连续素数的时候n值最小。
第二条:如果数值小的素数的幂次小于数值大的素数的幂,那么如果把这两个素数交换位置(幂次不变),那么所得的n因子数量不变,但是n的值变小,直到符合
条件。
另外还有两个问题,
1.对于给定的n,要枚举到哪一个素数呢?
最极端的情况大不了就是
,所以只要连续素数连乘到刚好小于等于n就可以的呢。再大了,连全都一次幂,都用不了,当然就是用不到的啦!
2.我们要枚举到多少次幂呢?
我们考虑一个极端情况,当我们最小的素数的某个幂次已经比所给的n(的最大值)大的话,那么展开成其他的形式,最大幂次一定小于这个幂次。unsigned long long 的最大值是2的64次方,所以我这边习惯展开成2的64次方。
细节有了,那么我们具体如何具体实现呢?
我们可以把当前走到每一个素数前面的时候列举成一棵树的根节点,然后一层层的去找。找到什么时候停止呢?
1.当前走到的数字已经大于我们想要的数字了
2.当前枚举的因子已经用不到了(和1重复了嘻嘻嘻)
3.当前因子大于我们想要的因子了
4.当前因子正好是我们想要的因子(此时判断是否需要更新最小ans)
然后dfs里面不断一层一层枚举次数继续往下迭代就好啦~~
常见题型有下面这两种。
1.给定因子数,求满足因子数恰好等于这个数的最小数。
对于这种题,我么只要以因子数为dfs的返回条件基准,不断更新找到的最小值就可以了
上代码:
#include
#define ULL unsigned long long
#define INF ~0ULL
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
ULL ans;
int n;
//depth:当前在枚举第几个素数。num:当前因子数。
//temp:当前因子数量为num的时候的数值。up:上一个素数的幂,这次应该小于等于这个幂次嘛
void dfs(int depth,int temp,int num,int up){
if(num > n||depth >= 16) return;
if(num == n&&ans > temp){
ans = temp;
return;
}
for(int i = 1;i <= up;i++){
if(temp/p[depth] > ans) break;
dfs(depth+1,temp = temp*p[depth],num*(i+1),i);
}
}
int main(){
while(scanf("%d",&n) != EOF){
ans = INF;
dfs(0,1,1,64);
printf("%d\n",ans);
}
return 0;
}
如有问题请留言。
2.给定一个n,求n以内因子数最多的数。
思路同上,只不过要改改dfs的返回条件。注意这样的题目的数据范围,我一开始用了int,应该是溢出了,在循环里可能就出不来了就超时了。上代码,0ms过。注释就没必要写了上面写的很清楚了。
#include
#include
#define ULL unsigned long long
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
ULL n;
ULL ans,ans_num;//ans为n以内的最大反素数(会持续更新),ans_sum为ans的因子数。
void dfs(int depth,ULL temp,ULL num,int up){
if(depth >= 16||temp > n)return;
if(num > ans_num){
ans = temp;
ans_num = num;
}
if(num == ans_num&&ans > temp)
ans = temp;
for(int i = 1;i <= up;i++){
if(temp*p[depth] > n)break;
dfs(depth+1,temp *= p[depth],num*(i+1),i);
}
return;
}
int main(){
while(scanf("%lld",&n) != EOF){
ans_num = 0;
dfs(0,1,1,60);
printf("%lld\n",ans);
}
return 0;
}
3.如果要求的是一个区间
的反素数,并不是一个连续区间
那么不能用上面的代码了。Problem - 2521acm.hdu.edu.cn
此时选出的数字并不是严格意义上的反素数。不满足性质1,2.比如区间【10,11】感谢大佬!!!但是由于区间特别特别小,所以可以暴力求解。
其余方法暂时还没想出来。
斜体为错误分析
这时有两个地方需要改动。
1.要在更新的时候加一个判断条件,即更新的数字是否在区间
内。
2.由于这时的数字已经不是严格意义上的反素数,所以不再符合性质2。因为遵循这个条件不一定能枚举到区间所有的数字。
对于性质一,其实是可以的,因为没有两个连续不能分解的数字,一旦给定区间,肯定有能够分解的数字。
错误的AC代码:
#include
#include
#define ULL unsigned long long
#define INF ~0
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
ULL n,m;
ULL ans,ans_num;//ans为m,n内的最大反素数(会持续更新),ans_num为ans的因子数。
void dfs(int depth,ULL temp,ULL num){
if(depth >= 7||temp > n)return;
if(temp >= m&&num > ans_num){
ans = temp;
ans_num = num;
}
if(num == ans_num&&ans > temp&&temp >= m)
ans = temp;
for(int i = 1;;i++){
if(temp > n/p[depth])break;
dfs(depth+1,temp *= p[depth],num*(i+1));
}
return;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&m,&n);
if(m == n){
printf("%lld\n",m);
continue;
}
ans = INF;
ans_num = 0;
dfs(0,1,1);
printf("%lld\n",ans);
}
return 0;
}
QAQ