我是由这样一道题接触到反素数的:
当时第一次看见这道题就懵逼了,30分暴力很好写。然后再想了一下,要求因数最多的话就需要由尽量多的不同的质因数组成,然后就不知道应该怎么样去推了,但是我觉得如果新加进来一个质因数的话,可以分成它在原来的质因数里面和不在原来的质因数来分析,这个很显然如果不在,那肯定就等于原来的因数个数翻倍;但如果它原来就在里面,那应该怎么分析呢?初步觉得应该是和用算术基本定理表示出来的各个素数头顶上的系数有关。
然后我就发现了万恶的约数个数定理:
对于一个大于1正整数n可以分解质因数:
![]()
则n的正约数的个数就是。
其中a1、a2、a3…ak是p1、p2、p3,…pk的指数。
我真的是bi了狗了。
那么这样看来,那算法就是循环从1-29的素数,然后根据这个定理求出他们的约数个数。不过有一点需要注意的就是我们尽量从小的素数开始枚举,而且指数一定要严格的不下降。(也就是接下来的性质2);
好,接下来开始正式讲解反素数:
首先看一下定义:
反素数的定义:对于任何正整数x,其约数的个数记做g(x).例如g(1)=1,g(6)=4.如果某个正整数x满足:对于任意i属于0到x,都有g(i)<g(x),则称x为反素数
从反素数的定义中可以看出两个性质:
性质一:一个反素数的质因子必然是从2开始连续的质数.
性质二:p=2^t1*3^t2*5^t3*7^t4…..必然t1>=t2>=t3>=….
这里给出代码:
#define ll long long
ll bestNum; //约数最多的数
ll bestSum; //约数最多的数的约数个数
const ll M=1000; //反素数的个数
ll n=500000;//求n以内的所有的反素数
ll rprim[M][2]; //2*3*5*7*11*13*17>n,所以只需考虑到17即可
ll prim[14]={2,3,5,7,11,13,17,19,23,29};
void getNum(ll num,ll k,ll sum,ll limit) //当前走到num这个数,接着用第k个素数,num的约数个数为sum,第k个素数的个数上限为limit
{
if(num>n)return;
if(sum>bestSum)
{
bestSum = sum;
bestNum = num;
}
else if(sum == bestSum && num < bestNum) //约数个数一样时,取小数
{
bestNum = num;
}
if(k>=7) return; //只需考虑到素数17,即prim[6]
for(ll i=1,p=1;i<=limit;i++)//素数k取i个
{
p*=prim[k];
getNum(num*p,k+1,sum*(i+1),i);
}
}
ll log2(ll n) //求大于等于log2(n)的最小整数
{
ll i=0;
ll p=1;
while(p<n)
{
p*=2;
i++;
}
return i;
}
ll getrprim()//反素数的个数
{
ll i = 0;
while(n>0)
{
bestNum = 1;
bestSum = 1;
getNum(1,0,1,log2(n));
n=bestNum-1;
rprim[i][0]=bestNum;
rprim[i][1]=bestSum;
i++;
}
return i;
}
最后rprim数组里记的就是所有的反素数了。
最后给出最开始一道题的代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#define ll ll
using namespace std;
ll v[50005],prime[50005],cnt,l,r,ans,anss,p;
ll _read() //输入输出优化
{
ll r=0,z=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')z=-1;c=getchar();}
while(c>='0'&&c<='9')r=r*10+c-'0',c=getchar();
return r*z;
}
void makeprime() //求出50以内的素数
{
for(ll i=2;i<=50;i++)
{
if(!v[i])prime[++cnt]=i;
for(ll j=1;j<=cnt&&i*prime[j]<=50;j++)
{
v[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
void dfs(ll st,ll now,ll step,ll ansx)
{
if(now>r)return;
if(now>=l&&now<=r)
{
p=ansx*(step+1);
if(p>anss)anss=p,ans=now;
else if(p==anss&&now<ans)ans=now;
}
for(ll i=st;i<=cnt;i++)
{
if(i==st)dfs(st,now*prime[i],step+1,ansx);
else dfs(i,now*prime[i],1,ansx*(step+1));
}
}
int main()
{
freopen("MFactor.in","r",stdin);
freopen("MFactor.out","w",stdout);
makeprime();
l=_read(),r=_read();
if(r<=100000) //如果数据范围小于100000,直接暴力
{
ll maxx=0;
for(ll i=l;i<=r;i++)
{
ll k=i;cnt=0;
for(ll j=1;j*j<=i;j++)
{
if(k%j==0)
{
cnt++;
p=k/j;
if(j!=p&&k%p==0)cnt++;
}
}
if(cnt>maxx)maxx=cnt,ans=i;
}
}
else dfs(1,1,0,1);
cout<<ans;
return 0;
}
这里是用第一段代码写的那道题:
#include<stdio.h>
#define LL long long
inline int _read(){
char c=getchar();
while(c<'0'||c>'9')c=getchar();
int t=0;
while(c>='0'&&c<='9')t=t*10+c-'0',c=getchar();
return t;
}
int v[50],cnt=0,L,R,bestSum=0,bestNum=0;
LL prim[14]={2,3,5,7,11,13,17,19,23,29};
void getNum(LL num,LL k,LL sum,LL limit)
{
if(num>R)return;
if(num>=L&&num<=R){
if(sum>bestSum)
{
bestSum = sum;
bestNum = num;
}
else if(sum == bestSum && num < bestNum)
{
bestNum = num;
}
}
if(k>=7) return;
getNum(num,k+1,sum,limit);
for(LL i=1,p=1;i<=limit;i++)
{
p*=prim[k];
getNum(num*p,k+1,sum*(i+1),i);
}
}
int main(){
scanf("%d%d",&L,&R);
getNum(1,0,1,30);
printf("%d",bestNum);
}
其中最精华的就是其中的一步
getNum(num,k+1,sum,limit);
使得这个方法求的不只是反质数,而是某一范围内因数最多且最小的数,因为反质数不一定是在给出的范围之内。
他这个方法就是不一定所有质因数都要是连续的,有时可以跳一个质因数才能取到最大值,所以我们在搜索的时候可以加一条选项,就是跳过当前这个质数,不取。这也就是反素数筛选法的灵活之处。