反素数:
对于任何正整数x,其约数的个数记作g(x)。例如g(1)=1、g(6)=4。
如果某个正整数x满足:g(x)>g(i) 0 < i < x,则称x为反质数。例如,整数1,2,4,6等都是反质数。
即是区间中约数最多的那个数。
性质:
1.一个反素数的质因子必然是从2开始连续的质数
2.N=p1^a1 *p2^a2 *…*pn^an,必然有a1>=a2>=a3…>=an
第一种题型:求1-n中因子数最多但数值最小的数
第二种题型:给一个数n表示恰好有n个因子,求满足条件的最小数
第三种题型:给一个区间[a,b],求这个区间里因子数最多但数值最小的数
思路:
(1)此题最容易想到的是穷举,但是肯定超时。
(2)我们可以知道,计算约数的个数和质因数分解有着很大的联系: 若Q的质因数分解为:Q=p1^k1*p2^k2*…*pm^km(p1…pm为素数,k1…km≥1),则Q有(k1+1)(k2+1)…(km+1)个约数。但是质因数分解的时间复杂度很高,所以也会超时。
(3)通过以上的公式,我们可以“突发奇想”:为何不能把质因数分解的过程反过来呢? 这个算法就是枚举每一个素数。初始时让m=1,然后从最小的素数2开始枚举,枚举因子中包含0个2、1个2、2个2…k个2,直至m*2^k大于区间的上限N。在这个基础上枚举3、5、7……的情况,算出现在已经得到的m的约数个数,同时与原有的记录进行比较和替换。直至所有的情况都被判定过了。 这个算法的优化:如果p1*p2*p3*……*pk>N(pi表示第i个素数),那么只要枚举到p k-1,既不浪费时间,也不会遗漏。
(4)以上的算法还不是最好的,还可以继续优化。 我们看以下的例子: 6=2*3 10=2*5 6和10的质因数分解“模式”完全相同,所以它们的约数个数是相同的。但是由于3<5,所以6<10。 12=2^2*3 18=3^2*2 12和18的质因数分解“模式”完全相同,所以它们的约数个数是相同的。但是由于12的质因数分解中2的指数大于3的指数,18的质因数分解中3的指数大于2的指数,所以12<18。 根据以上的举例,我们也可以对(3)中的算法进行一个改进:可以在枚举时进行一个优化,使得枚举到的数字中2的指数不小于3的指数,3的指数不小于5的指数……这样我们就能够得到质因数分解“模式”相同的最小数(证明略)。再对于每一个得到的数进行比较和记录。这个算法的优化力度极大,效率几乎达到了极限。
第一种题型:
题目链接:http://codeforces.com/problemset/problem/27/E
ac代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#define ll long long
using namespace std;
const ll maxx=1e18+9;
const ll p[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
const ll INF=0x3f3f3f3f;
ll res,maxnum;
ll t,n;
void dfs(ll now,ll num,ll x,ll n){
if(num>n) return ;
if(num==n&&res>now){
res=now;
return ;
}
for(int i=1;i<=64;i++){
if(now*p[x]>res)
break;
else{
dfs(now*=p[x],num*(i+1),x+1,n);
}
}
}
int main(){
cin>>n;
res=INF*INF;
maxnum=0;
dfs(1,1,0,n);
cout<<res<<endl;
return 0;
}
第二种题型:
题目链接:https://vjudge.net/problem/11177
ac代码:
#include <stdio.h>
#include <string.h>
#include <iostream>
#define LL long long
using namespace std ;
LL n,minnum,cnt ;
const int prime[20] = {1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47} ;
//num:当前枚举到的数,k:枚举到的第k大的质因子;cntt:该数的约数个数;maxxcnt:质因子个数上限;
void dfs(LL num,LL k,LL cntt,int maxxcnt)//求1-n中因子数最大、数值最小的数
{
if(k >= 16) return ;
//如果约数个数更多或者相同,将最优解更新为当前数;
if(cntt > cnt || (cntt == cnt && num < minnum))
{
cnt = cntt ;
minnum = num ;
}
LL temp = num ;
for(LL i = 1 ; i <= maxxcnt ; i++) //开始枚举每个质因子的个数;
{
if(temp > n / prime[k])
break ;
temp *= prime[k] ; //累乘到当前数;
dfs(temp,k+1,cntt*(i+1),i) ;
}
}
int main()
{
int T ;
scanf("%d",&T) ;
while(T--)
{
scanf("%I64d",&n) ;
minnum = cnt = 1 ;
dfs(1,1,1,50) ;
printf("%I64d %I64d\n",minnum,cnt) ;
}
return 0 ;
}
第三种题型:
题目链接:http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1203
思路:
求解区间内[a,b]最小反素数
用4思路啊,不行,为啥,因为4只关心约数个数,没考虑过区间问题,很明显2^2*3 4的思路限制了3的个数最多只能是2,
举个例子[150,150]你怎么破?150 = 2*3*5*5,这种情况,在4中是不可能的,所以只能用3了
首先,数据范围是n<=1e9,数据太大,如何快速算出来呢?我们注意到,如果是暴力算的话,最快的方法就是分解质因子,然后组合式计算啦。但是在算18和30的约数的时候,他们的
gcd(18,30)=6,其实是被重复算了的,那么我们思维反过来一下,把分解质因数变成用质因数去组合,使得变成区间内的数,这样一来,我们在2*3的时候,*3就得到了18,*5就得到了30,能省掉一定的时间。但是还是会TLE。假如我们现在枚举到的数是now,会不会它的约数根本就没可能存在于区间里呢?也就是[begin,end]根本就没这些约数。[7,11]内是不会存在6的倍数的。如果[1,begin-1]中6的约数和[1,end]中6的约数相同,说明什么?新加进去的区间[begin,end]根本就没6的约数,这里可以剪枝。还是TLE!!可行性剪枝,如果一个数是now,现在枚举一个新的质数去乘以它,去结合成新的数字,那么如果它无论组成什么其他数字,因子个数都不会超过当前最优值mx呢?怎么判断呢?放缩咯,假如现在是2*3,重新去匹配一个新的素数5,那么,我就要看,当前2*3还能再乘多少个3呢?我记作q,那么这个新的匹配,最理想的情况下因子个数会多2q倍,为什么呢?把那些3,全部替换成5*7*11*13这样来算的话,就是有2q个了。别以为这样没用,当你搜[1,1e9]的时候,你枚举到8000w,再去枚举5那些是没用的,根本就不可能,这里能剪很多。
其实我们还有一个根本的问题没解决,那就是预处理素数到多大,还有万一它是大素数呢?
想着预处理多少,要看数据,预处理出来的最大质数,primeMax‑2是要大过1e9才行的。为什么呢?因为你只有这样,才能防止它数据是两个大质数相乘的形式[primeMax2,primeMax2]。这里的因子个数是3,你枚举不到这个primeMax的话,就只能得到2。
还有那个大素数,没什么怕的,如果当前那个数now,幻想它乘以一个大质数,还是在end的范围的话,就看看*2和mx谁大咯。乘以一个大素数也才加一倍因子数。其实乘以一个小的质因子的话,因子数会更多,这里主要是判断只有一个大素数的特殊情况。枚举不到那个大素数那里的。
ac代码:
/*
* noj1203
* 最多约数问题
* 正整数x的约数是能整除x的正整数。正整数x的约数个数记为div(x)。例如,1,2,5,10都是正整数10的约数,且div(10)=4。 对于给定的2个正整数a<=b,编程计算a与b之间约数个数最多的数。
* a b的范围大约在1e9,遍历a,b且并计算约数的大小并比较必然不行
* 一开始想用2,3,4,5。。。去筛那个区间,但是数组开不下
* 所以先筛出100000以内的素因子
* 首先我们知道正整数k分解质因数为k=p1^n1*p2^n2*p3^n3*...*pk^nk,则它的约数的个数则为(n1+1)*(n2+1)*(n3+1)*...*(nk+1)
* 所以枚举素因子,看累积在[a,b]里的结果的约数的个数。
* 也就是说,先枚举2*2*2*2...直到>b,然后2*2*2*..*3...这样枚举。
* 大素数的问题,如果有一个大于100000的大素数怎么办,就看当前乘积是否小于a,且乘上prime[cnt-1]是否小于b,是则将当前约数乘2与Max比较,即假设有一个大素数。
* 几个剪枝:
* 1、当前乘积超过b,剪掉
* 2、如果当前扫到素数5,设当前乘积为k,则约数最大能乘上2^q,q=log(b/now)/log(5);如果这样还比当前Max小,则直接剪掉。
* 3、如果当前乘积为k<a,而(a-1)/k=b/k,则[a,b]不存在k的整数倍,直接剪掉。
*/
/*************************************************************************
> File Name: 1203.cpp
> Author: UnknownCUnknown
> Mail: jsnjhcb@icloud.com
> Created Time: 二 12/16 20:12:34 2014
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <list>
#include <string>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <climits>
using namespace std;
int maxn=100000;
int prime[100010];
bool isprime[100010];
int cnt=0;
void init(){//素数筛
memset(isprime,true,sizeof isprime);
isprime[0]=isprime[1]=false;
for(int i=2;i<=maxn;++i){
if(isprime[i]){
for(int j=i+i;j<=maxn;j+=i){
if(isprime[j]) isprime[j]=false;
}
}
}
for(int i=2;i<=maxn;++i){
if(isprime[i]){
prime[cnt]=i;
++cnt;
}
}
}
long long a,b;//区间
int Max=0;//最大因子数
long long qpow(long long x,long long n){//快速幂求x^n
long long result=1;
while(n){
if(n&1){
result*=x;
}
x*=x;
n/=2;
}
return result;
}
void dfs(int point,int cnt1,long long now,long long num){
if(now>b){//如果超上限
return;
}
long long k=num*(cnt1+1);//计算当前因子数
if(now>=a){//如果在区间里并且当前因子数更大,更新
if(k>Max) Max=k;
}
for(int i=point;i<cnt&&prime[i]<=b;++i){
long long tmp=now*prime[i];
if(tmp>b) return;//第一种情况剪枝
if(i==point){//如果不是大素数
dfs(i,cnt1+1,tmp,num);
}
else {//如果是大素数
long long k=num*(cnt1+1);
long long rest=b/now;
long long q=log(rest)/log(prime[point]);
long long r2q=qpow(2,q);
if(r2q*k<=Max) return;//第二种情况剪枝
if((a-1)/now==b/now) return;//第三种情况剪枝
else if(now<a&&b/now>prime[cnt-1]){
if(2*k>Max) Max=2*k;//k*(1+1)如果比MAx大就更新
}
dfs(i,1,tmp,k);//重新从第一个素数搜
}
}
}
int main(){
init();
scanf("%lld %lld",&a,&b);
dfs(0,0,1,1);
printf("%d\n",Max);
return 0;
}