反素数 &[BSOJ1414] Antiprime数& [BSOJ4861] 最多因数 & [LuoguP1221] 最多因子数 题解

本文详细介绍了反素数的概念,包括定义、性质,并通过例题讲解了如何求解反素数及最多因数的问题。文章讨论了性质一(质因数指数上升)和性质二(质因子连续),并提供了利用DFS解决此类问题的思路和边界条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

反素数

文章参考(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) (x1)这几个数中没有任意一个数的因数个数大于 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=a1b1a2b2a2b2akbk,则必有 b 1 ≥ b 3 ≥ ⋅ ⋅ ⋅ ≥ b k b_1\ge b_3\ge ···\ge b_k b1b3bk
    这点很好理解,借助贪心的思想我们思考:首先要让因子数量尽可能的多,只需要把质因数的次数变多,或者质因数变多。但是还得满足让数值尽可能的小,那么就要让小的质因数的次数尽可能的大
  • 性质二:一个反素数的质因子必然是从2开始连续的质数
    举个例子: 6 = 2 ∗ 3 , 10 = 2 ∗ 5 6=2*3,10=2*5 6=23,10=25 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;
}


[Luogu]传送门 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;
}

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值