一开始做这道题http://codeforces.com/problemset/problem/27/E 才知道用反素数
之前之前完全不知道反素数是什么,那就从头开始学起
反素数
定义
官方定义:对于任何正整数,其约数个数记为,例如,如果某个正整数满足:对任意的正整
数,都有,那么称为反素数。
通俗来说,反素数就是在一个集合中因子最多并且值最小的数。
//换一种说法,素数是只含本身和1两个因子的数,那么反素数,就是有很多因子的数。
特点
解释:
- 反素数肯定是从2开始的连续素数的幂次形式的乘积。
- 数值小的素数的幂次大于等于数值大的素数,即中,有
1:如果不是从2开始的连续素数,那么如果幂次不变,把素数变成数值更小的素数,那么此时因子个数不变,但是n的
数值变小了。交换到从2开始的连续素数的时候n值最小。
2:如果数值小的素数的幂次小于数值大的素数的幂,那么如果把这两个素数交换位置(幂次不变),那么所得的n因子数量不变, 但是n的值变小,直到符合条件。
//我不是很理解这两条特点,但是先记着嘛,为了做题而做题
模板
首先,既然要求因子数,首先想到的就是素因子分解。把n分解成的形式,其中p是素数,k为他的指数。这样的话就是总因子个数。
//depth:当前在枚举第几个素数
//num:当前因子数
//temp:当前因子数量为num的时候的数值
//up:上一个素数的幂,这次应该小于等于这个幂次
void dfs(int depth,int temp,int num,int up)
{
if(num>n || depth>=16) return; //如果因子数比n大
if(num==n && ans>temp)
{
ans=temp; //ans事先赋值无穷大
return; //求最优ans 最小ans
}
for(int i=1;i<=up;i++)
{
if(num*(i+1)>n) break; //如果搜索到此的因子数目超过了n,那么不符合条件
dfs(depth+1,temp=temp*p[depth],num*(i+1),i);
//temp这样赋值是因为乘上了这个因子
//num这样赋值是因为有特点2可知因子数这样改变
}
}
几种常见题型
1
给定因子数,求满足因子数恰好等于这个数的最小数。
题目链接:http://codeforces.com/problemset/problem/27/E
题意:给一个数n,求一个最小的正整数,使得它的因子个数为n
#include<stdio.h>
#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;
if(num*(i+1)>n) break; //如果因子数超过了n 那么break
dfs(depth+1,temp=temp*p[depth],num*(i+1),i);
}
}
int main(){
while(scanf("%d",&n)!=EOF){
ans=NF;
dfs(0,1,1,64);
printf("%d\n",ans);
}
return 0;
}
2
给定一个n,求n以内因子数最多的数。
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1562
较上一道题的变化是
ULL的变化 还有 dfs(0,1,1,60); 而不是 dfs(0,1,1,64);
测试发现 dfs(0,1,1,64)会超时,而 dfs(0,1,1,60); dfs(0,1,1,50);都AC
dfs退出的终止条件改变了,temp>n超过了范围就退出
要求得因子最多的数,用ans_sum控制
当两个数因子数目相同时,选择ans较小的那一个数
#include<cstdio>
#include<iostream>
#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 ans,ans_num;
ULL n;
//depth:当前在枚举第几个素数
//num:当前因子数
//temp:当前因子数量为num的时候的数值
//up:上一个素数的幂,这次应该小于等于这个幂次
void dfs(int depth,ULL temp,ULL num,ULL up)
{
if(temp>n || depth>=16) return;
if(num>ans_num)
//if(num==n && ans>temp)
{
ans=temp;
ans_num=num;
}
//当因子个数相同时,取值最小的
if(num==ans_num && ans>temp) ans=temp;
for(int i=1;i<=up;i++)
{
//if(temp/p[depth]>ans) break;
if(num*(i+1)>n) break; //如果因子数超过了n 那么break
dfs(depth+1,temp=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
给定区间,求区间内因子数最多的数。
http://acm.hdu.edu.cn/showproblem.php?pid=2521
这时有两个地方需要改动。
1.要在更新的时候加一个判断条件,即更新的数字是否在区间内。
2.由于这时的数字已经不是严格意义上的反素数,所以不再符合性质2。因为遵循这个条件不一定能枚举到区间所有的数字。
对于性质一,其实有没有都可以,因为没有两个连续不能分解的数字,一旦给定区间,肯定有能够分解的数字。
#include<cstdio>
#include<iostream>
#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_sum为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(num*(i+1)>n) break; //如果因子数超过了n 那么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;
}
//