反素数
文章参考(ACdreamers)
1.定义
“于任何正整数x,其约数的个数记作g(x)。例如g(1)=1、g(6)=4。
如果某个正整数x满足:g(x)>g(i),0<i<x,则称x为反质数。例如,整数1,2,4,6等都是反质数。”
————度娘说的
什么意思呢?用自己的口水话解释一下:对于一个数
x
x
x,如果从
1
1
1 到
(
x
−
1
)
(x-1)
(x−1)这几个数中没有任意一个数的因数个数大于
x
x
x的,那么就称
x
x
x为反素数。
其实,反素数的本质就是尽可能地让因子数量最大,但是数值最小。
2.性质
从反素数的定义可以发现凡是反素数都满足两个性质:
- 性质一:对于所有反素数
x
=
a
1
b
1
∗
a
2
b
2
∗
a
2
b
2
∗
⋅
⋅
⋅
∗
a
k
b
k
x={a_1}^{b_1} *{a_2}^{b_2}*{a_2}^{b_2}*···*{a_k}^{b_k}
x=a1b1∗a2b2∗a2b2∗⋅⋅⋅∗akbk,则必有
b
1
≥
b
3
≥
⋅
⋅
⋅
≥
b
k
b_1\ge b_3\ge ···\ge b_k
b1≥b3≥⋅⋅⋅≥bk
这点很好理解,借助贪心的思想我们思考:首先要让因子数量尽可能的多,只需要把质因数的次数变多,或者质因数变多。但是还得满足让数值尽可能的小,那么就要让小的质因数的次数尽可能的大 - 性质二:一个反素数的质因子必然是从2开始连续的质数
举个例子: 6 = 2 ∗ 3 , 10 = 2 ∗ 5 6=2*3,10=2*5 6=2∗3,10=2∗5 6 6 6和 10 10 10因子数目相同,显然 6 6 6更小。直接跳过一个小一点的质数,放一个大的质数显然会使数值变得更大。
3.例题
[BSOJ1414] Antiprime数
传送门(题目应是在Antiprime数因数个数最多的情况下输出最小的一个)
这道题是一道模板题。(这范围不像搜索啊……)
大致思路是:从最小的质数(2)开始用
D
f
s
Dfs
Dfs枚举每个质数的指数,根据性质一,指数应是不上升的。每次
D
f
s
Dfs
Dfs传递四个值:
- deep:枚举到第几个质数了
- arr:当前质数的最大指数(当前指数 ≤ \leq ≤上一个质数的指数)
- cur:从开始枚举到当前共有多少因数
- num:从开始枚举到当前所有枚举的质数组成的数(幂之后相乘)
每次 D f s Dfs Dfs开始时传递的值是上一次枚举的结果,所以先保存值,再判定边界,最后循环枚举。
void Dfs(int deep,int arr,int cur,ll num){
if(cur>maxn||(cur==maxn&&num<ans))
maxn=cur,ans=num;
if(deep>=9)
return;
for(register int i=1;i<=arr;i++){
num*=prime[deep];
if(num>n)
return;
Dfs(deep+1,i,(i+1)*cur,num);
}
}
那边界又是什么呢?应该枚举多少个质数呢?(先去看看下面的完整代码吧)
我们注意到有关边界的有四处:
-
if(num>n)
这个好理解:当这次枚举的结果超过范围了,自然不用向下枚举了,return。 -
ll prime[15]={2,3,5,7,11,13,17,19,23,29,31,37};
if(deep>9)
为什么只需要这9(prime[0]~prime[8])个质数呢? 原因很简单:首先,因为是反素数,所以因数要多,数本身值要小,所以是前9个质数。其次,我们把每9个质数都只取一个,此时组成的数已是223,092,870了,再多1个29的话就会超过范围2,000,000,000了。(其实多算几个也没错) -
Dfs(0,31,1,1);
为什么第一个初值传31呢? 原因也很简单:第一个质数是2,那最多能选多少个2相乘呢? ——31个, 2 31 = 2 , 147 , 483 , 648 2^{31}=2,147,483,648 231=2,147,483,648恰好大于范围。
这。。。这不是暴力吗?? 要这个干嘛?
for(register ll i=1;i<=n;i++){
ll cnt=0;
for(register ll j=1;j*j<=i;j++)
if(i%j==0){
cnt++;
ll temp=i/j;
if(i%temp==0&&temp!=j)
cnt++;
}
if(cnt>maxn)
maxn=cnt,ans=i;
}
首先,这个暴力的复杂度在一定范围内是可以接受的。问题就来了不是有
D
f
s
Dfs
Dfs了吗?还要暴力做什么? 我们思考这样一种毒瘤数据 情况,当L,R相距很近时,在这个区间中不存在反素数,但仍有答案。这种数据出现在枚举的区间很小情况下,便可以用暴力解决。至于区间多大时该用暴力,这个没有固定的值,只有根据题的范围来确定,不过我通常用100,000 or 200,000。
AC代码 运行结果
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
typedef long long ll;
template <typename T>
inline void read(T&x){
x=0;int temp=getchar();bool f=false;
while(temp<'0'||temp>'9'){if(temp=='-') f=true; temp=getchar();}
while(temp>='0'&&temp<='9'){x=(x<<1)+(x<<3)+temp-'0'; temp=getchar();}
if(f) x=-x;
}
void print(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+'0');
}
ll n,maxn,ans;
ll prime[15]={2,3,5,7,11,13,17,19,23,29,31,37};
void Dfs(int deep,int arr,int cur,ll num){
if(cur>maxn||(cur==maxn&&num<ans))
maxn=cur,ans=num;
if(deep>9)
return;
for(register int i=1;i<=arr;i++){
num*=prime[deep];
if(num>n)
return;
Dfs(deep+1,i,(i+1)*cur,num);
}
}
int main(){
read(n);
if(n<=200000){
for(register ll i=1;i<=n;i++){
ll cnt=0;
for(register ll j=1;j*j<=i;j++)
if(i%j==0){
cnt++;
ll temp=i/j;
if(i%temp==0&&temp!=j)
cnt++;
}
if(cnt>maxn)
maxn=cnt,ans=i;
}
}
else
Dfs(0,31,1,1);
print(ans);
return 0;
}
[BSOJ4861] 最多因数 & [LuoguP1221] 最多因子数
这道题和上一道题其实差不多,上一道题只是把左端点设为1了。
[BSOJ]传送门 AC代码 运行结果
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
typedef long long ll;
template <typename T>
inline void read(T&x){
x=0;int temp=getchar();bool f=false;
while(temp<'0'||temp>'9'){if(temp=='-') f=true; temp=getchar();}
while(temp>='0'&&temp<='9'){x=(x<<1)+(x<<3)+temp-'0'; temp=getchar();}
if(f) x=-x;
}
void print(ll x){
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+'0');
}
ll l,r;
ll ans,maxn=0;
ll prime[15]={2,3,5,7,11,13,17,19,23,29,31,37};
void Dfs(int deep,int arr,int cur,ll num){
if(maxn<cur||(maxn==cur&&num<ans))
maxn=cur,ans=num;
if(deep>8)
return;
for(register int i=1;i<=arr;i++){
num*=prime[deep];
if(num>r)
return;
Dfs(deep+1,i,cur*(i+1),num);
}
}
int main(){
read(l),read(r);
if(r-l<=100000){
ll maxn=0;
for(register ll i=l;i<=r;i++){
ll cnt=0;
for(register ll j=1;j*j<=i;j++)
if(i%j==0){
cnt++;
ll temp=i/j;
if(temp!=j&&i%temp==0)
cnt++;
}
if(cnt>maxn)
maxn=cnt,ans=i;
}
}
else
Dfs(0,30,1,1);
print(ans);
return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
typedef long long ll;
template <typename T>
inline void read(T&x){
x=0;int temp=getchar();bool f=false;
while(temp<'0'||temp>'9'){if(temp=='-') f=true; temp=getchar();}
while(temp>='0'&&temp<='9'){x=(x<<1)+(x<<3)+temp-'0'; temp=getchar();}
if(f) x=-x;
}
ll prime[15]={2,3,5,7,11,13,17,19,23,29,31,37,41};
ll l,r;
ll maxn=0,ans,cnt;
void Dfs(int deep,int arr,int cur,ll num){
if(maxn<cur||(maxn==cur&&num<ans))
maxn=cur,ans=num;
if(deep>8)
return;
for(register int i=1;i<=arr;i++){
num*=prime[deep];
if(num>r)
return;
Dfs(deep+1,i,cur*(i+1),num);
}
}
int main(){
read(l),read(r);
if(r-l<=100000){
for(register ll i=l;i<=r;i++){
cnt=0;
for(register int j=1;j*j<=i;j++)
if(i%j==0){
cnt++;
ll temp=i/j;
if(i%temp==0&&temp!=j)
cnt++;
}
if(cnt>maxn)
ans=i,maxn=cnt;
}
}
else
Dfs(0,31,1,1);
printf("Between %lld and %lld, %lld has a maximum of %lld divisors.",l,r,ans,maxn);
return 0;
}