反素数深度分析【转载】


太强大了!分析令人震颤!
ACdtramers大佬此文章的传送门
http://blog.csdn.net/ACdreamers/article/details/25049767

今天要我要讲的是反素数,在ACM中也算是常见的考点,那么对于搞ACM的同学来说,很有必要搞清楚它,所以接下来我会很详细地讲解。
在讲解反素数之前,我们先来看反素数的概念。
反素数的定义:对于任何正整数,其约数个数记为,例如,如果某个正整数满足:对任意的正整数,都有,那么称为反素数。
从反素数的定义中可以看出两个性质:
(1)一个反素数的所有质因子必然是从2开始的连续若干个质数,因为反素数是保证约数个数为的这个数尽量小
(2)同样的道理,如果,那么必有t1>=t2>=t3>=….

在ACM竞赛中,最常见的问题如下:
(1)给定一个数,求一个最小的正整数,使得的约数个数为
(2)求出中约数个数最多的这个数
从上面的性质中可以看出,我们要求最小的,它的约数个数为,那么可以利用搜索来解。
以前我们求一个数的所有因子也是用搜索,比如,以每一个为树的一层建立搜索树,深度为以为例进行说明,建树如下:
这里写图片描述


以看出从根节点到每一个叶子结点这条路径上的所有数字乘起来都是12的约数,所以12有6个约数。
搜索的思路就明显了,从根节点开始进行深搜,到叶子结点,代码如下:

void dfs(int dept,LL ans = 1)  
{  
    if(dept == cnt)  
    {  
        fac[ct++] = ans;  
        return;  
    }  
    for(int i=0;i<=num[dept];i++)  
    {  
        dfs(dept+1,ans);  
        ans *= pri[dept];  
    }  
}  

回到我们的问题,同样用搜索来求最小的。
题目:http://codeforces.com/problemset/problem/27/E
题意:给一个数,求一个最小的正整数,使得它的因子个数为。
分析:与求因子的方法类似,先建立搜索树,以每一个为一层建立树型结构,进行搜索,取最小的

#include <iostream>  
#include <string.h>  
#include <stdio.h>  

using namespace std;  
typedef unsigned long long ULL;  
const ULL INF = ~0ULL;  

int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};  

int n;  
ULL ans;  

void dfs(int dept,ULL tmp,int num)  
{  
    if(num > n) return;  
    if(num == n && ans > tmp) ans = tmp;  
    for(int i=1;i<=63;i++)  
    {  
        if(ans / p[dept] < tmp) break;  
        dfs(dept+1,tmp *= p[dept],num*(i+1));  
    }  
}  
int main()  
{  
    while(cin>>n)  
    {  
        ans = INF;  
        dfs(0,1,1);  
        cout<<ans<<endl;  
    }  
    return 0;  
}  

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1562
题意:求以内的因子最多的那个数。
分析:基本上跟上题差不多

#include <iostream>  
#include <string.h>  
#include <stdio.h>  

using namespace std;  
typedef unsigned long long ULL;  
const ULL INF = ~0ULL;  

int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};  

ULL ans,n;  
int best;  

void dfs(int dept,ULL tmp,int num)  
{  
    //到叶子结点,返回  
    if(dept >= 16) return;  
    //num记录的因子个数,如果遇到更小的,就更新  
    if(num > best)  
    {  
        best = num;  
        ans = tmp;  
    }  
    //当因子个数相同时,取值最小的  
    if(num == best && ans > tmp) ans = tmp;  
    for(int i=1;i<=63;i++)  
    {  
        if(n / p[dept] < tmp) break;  
        dfs(dept+1,tmp *= p[dept],num*(i+1));  
    }  
}  

int main()  
{  
    while(cin>>n)  
    {  
        ans = INF;  
        best = 0;  
        dfs(0,1,1);  
        cout<<ans<<endl;  
    }  
    return 0;  
}  

题目:http://acm.timus.ru/problem.aspx?space=1&num=1748
分析:这道题主要注意数据处理。对于上面的两题,数据范围小,所以可以不用剪枝,本题就需要了

#include <iostream>  
#include <string.h>  
#include <stdio.h>  

using namespace std;  
typedef unsigned long long ULL;  
const ULL INF = ~0ULL;  

int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};  

ULL ans,n;  
int best;  

void dfs(int dept,int limit,ULL tmp,int num)  
{  
    if(tmp > n) return;  
    if(num > best)  
    {  
        best = num;  
        ans = tmp;  
    }  
    if(num == best && ans > tmp) ans = tmp;  
    for(int i=1;i<=limit;i++)  
    {  
        double cur = (double)tmp;  
        if(n < cur*p[dept]) break;  
        dfs(dept+1,i,tmp *= p[dept],num*(i+1));  
    }  
}  

int main()  
{  
    int T;  
    cin>>T;  
    while(T--)  
    {  
        cin>>n;  
        ans = INF;  
        best = 0;  
        dfs(0,60,1,1);  
        cout<<ans<<" "<<best<<endl;  
    }  
    return 0;  
}  

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4542
题意:
给出一个数K和两个操作
如果操作是0,就求出一个最小的正整数X,满足X的约数个数为K。
如果操作是1,就求出一个最小的X,满足X的约数个数为X-K。
分析:对于操作0,就是求反素数,直接搜索搞定。对于操作1,代表1至X中不是X的约数个数为K。
代码

#include <iostream>  
#include <string.h>  
#include <stdio.h>  

using namespace std;  
const int N = 50005;  
typedef long long LL;  
const LL INF = (((LL)1)<<62)+1;  

int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};  

LL ans;  
int n;  
int d[N];  

void Init()  
{  
    for(int i=1;i<N;i++) d[i] = i;  
    for(int i=1;i<N;i++)  
    {  
        for(int j=i;j<N;j+=i) d[j]--;  
        if(!d[d[i]]) d[d[i]] = i;  
        d[i] = 0;  
    }  
}  

void dfs(int dept,int limit,LL tmp,int num)  
{  
    if(num > n) return;  
    if(num == n && ans > tmp) ans = tmp;  
    for(int i=1;i<=limit;i++)  
    {  
        if(ans / p[dept] < tmp || num*(i+1) > n) break;  
        tmp *= p[dept];  
        if(n % (num*(i+1)) == 0)  
            dfs(dept+1,i,tmp,num*(i+1));  
    }  
}  

int main()  
{  
    Init();  
    int T,tt=1;  
    scanf("%d",&T);  
    while(T--)  
    {  
        int type;  
        scanf("%d%d",&type,&n);  
        if(type) ans = d[n];  
        else  
        {  
            ans = INF;  
            dfs(0,62,1,1);  
        }  
        printf("Case %d: ",tt++);  
        if(ans == 0) puts("Illegal");  
        else if(ans >= INF) puts("INF");  
        else printf("%I64d\n",ans);  
    }  
    return 0;  
}  


还有黄学长博客
http://hzwer.com/3141.html
一个数约数个数=所有素因子的次数+1的乘积
举个例子就是48 = 2 ^ 4 * 3 ^ 1,所以它有(4 + 1) * (1 + 1) = 10个约数
然后可以通过计算得一个2000000000以内的数字不会有超过12个素因子
并且小素因子多一定比大素因子多要优
预处理出前12个素数直接爆搜即可

#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 0x7fffffff
#define ll long long 
using namespace std;
int n,ans=1,num=1;
int p[15]={1,2,3,5,7,11,13,17,19,23,29,31};
void dfs(int k,ll now,int cnt,int last)
{
    if(k==12)
    {
        if(now>ans&&cnt>num){ans=now;num=cnt;}
        if(now<=ans&&cnt>=num){ans=now;num=cnt;}
        return;
    }
    int t=1;
    for(int i=0;i<=last;i++)
    {
        dfs(k+1,now*t,cnt*(i+1),i);
        t*=p[k];
        if(now*t>n)break;
    }
}
int main()
{
    scanf("%d",&n);
    dfs(1,1,1,20);
    printf("%d",ans);
    return 0;
}


以及一名博客园大佬
http://www.cnblogs.com/tiankonguse/archive/2012/07/29/2613877.html
思维过程:
求[1..N]中最大的反素数–>求约数最多的数
如果求约数的个数 756=2^2*3^3*7^1
(2+1)(3+1)(1+1)=24
基于上述结论,给出算法:按照质因数大小递增顺序搜索每一个质因子,枚举每一个质因子
为了剪枝
性质一:一个反素数的质因子必然是从2开始连续的质数.
因为最多只需要10个素数构造:2,3,5,7,11,13,17,19,23,29
性质二:p=2^t1*3^t2*5^t3*7^t4…..必然t1>=t2>=t3>=….

typedef __int64 INT;
INT bestNum;   //约数最多的数
INT bestSum;   //约数最多的数的约数个数
const int M=1000; //反素数的个数 
INT n=500000;//求n以内的所有的反素数
INT rprim[M][2];
//2*3*5*7*11*13*17>n,所以只需考虑到17即可
INT prim[14]={2,3,5,7,11,13,17,19,23,29};  

//当前走到num这个数,接着用第k个素数,num的约数个数为sum,
//第k个素数的个数上限为limit
void getNum(INT num,INT k,INT sum,INT 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(INT i=1,p=1;i<=limit;i++){   //素数k取i个
        p*=prim[k];
        getNum(num*p,k+1,sum*(i+1),i);
    }
}

INT log2(INT n){   //求大于等于log2(n)的最小整数
    INT i = 0;
    INT p = 1;
    while(p<n){
        p*=2;
        i++;
    }
    return i;
}

int getrprim(){//反素数的个数
    int 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;    
}
发布了75 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36693514/article/details/78273906