算法 求解素數 和 快速求出一個數所有約數


如何快速求出區間范圍[0,x]內的素數?

Brute Force 總是那么的讓人覺得親近。

枚舉每一個數,判斷該數是否為素數?

bool isPrime( int x ) {
    if(x < 2) return 0;
    if(x == 2) return 1;
    for ( int i=2; i*i<=x; ++i )
      if(0 == x%i) return 0;
    return 1;
} 
// 用prime[0]存素數個數
for ( int i=0; i<=x; ++i ) if( isPrime(i) ) prime[++prime[0] ] = i;

暴力解決問題在很多時候都會存在效率低下這個問題。那么對於求素數這個問題如何提高效率呢?

其實很多數都是有公共因子的(公約數)。如果直接把這個公約數拿出來去踢出其倍數,被踢出的數都不會是素數的。例如:

2 可以踢出: 4 6 8 10 12 。。。2*x (x=2,3,4,5,6,7,8....)

3 可以踢出: 6 9 12 15 18。。。3*x(x=2,3,4,5,6,7,8....)

5 可以踢出: 10 15 20 25。。。5*x(x=2,3,4,5,6,7,8.....)

......... 依次做下去,剩余的數就是素數了。

int prime[3438];
bool isPrime[40007];

for ( int i=1; i<=x; ++i ) // 初始化所有的數都是素數
  isPrime[i] = 1;
for ( int i=2; i<=x; ++i ) if(isPrime[i] ) { // 該數是素數
    for ( int j=i+i; j<x; j+=i ) //踢出不是素數的數
      isPrime[j] = 0;
}
for ( int i=2; i<=x; ++i ) // 存取素數
  if(isPrime[i] ) prime[++prime[0] ] = i;

這樣看貌似效率是提高了不少額。可是仔細分析上面的踢出非素數的過程會發現,有些數被踢出了好幾次,為什么會出現這個問題呢?

x 可以踢出: x*2 x*3 x*4  x*(x-1) x*x x*(x+1).....

不難發現其實x*2 x*3 x*4....x*(x-1)這些被更小的因子給踢出了。也就是說對於x應該踢出的數是從

x*x開始的。那么是不是這就已經優化到頂了呢? 沒有哦。

開始考慮x*x是偶數,還是奇數。如果是偶數的話,x必然是偶數的。這中情況可以直接用素因子2直接把這些數踢出掉。

x*x是奇數,x必然是奇數了。 x*x+x是什么數呢? 是偶數。奇數+奇數=偶數。因為奇數%2=1,兩個1相加就是偶數了。這樣的話又會造成部分偶數被踢出多次。如何解決這個問題。呵呵,只需對x*x+x+x就好了,因為x*x+x是偶數 x*x+x+x,就是奇數了, 接下來的奇數就是x*x+2*(x+x)了。優化到此結束。

int prime[3438];
bool isPrime[40007];

void getPrime( int x ){
   for ( int i=1; i<x; i+=2 )
      isPrime[i] = 1, isPrime[i-1] = 0; // 首先踢出2的倍數
   prime[prime[0]=1 ] = 2; // 存取第一個素數
   for ( int i=3; ; i+=2 ) if(isPrime[i] ) { // 只考慮奇數了
       int j = i*i, k = i+i;
       if(j >= x) break;
       while(j < x ) {
           isPrime[j] = 0;  j += k;
       }
   }// 存素數
   for ( int i=3; i<x; i += 2 )
     if(isPrime[i]) prime[++prime[0] ] = i;
}

對次求解素數已經完畢了。

 接下來是將x分解成素因子的乘積

求x = p[1]^cnt[1]*p[2]^cnt[2]*p[3]^cnt[3]。。。。p[...]^cnt[...]  p[]是素因子

int p[3438], cnt[3438];

void getPrimeDivisor( int x ) {
     p[0] = cnt[0] = 0; int t;
     for ( int i=1; prime[i]*prime[i]<=x  && i<=prime[0]; ++i ) {
         t = 0;
         while( x%prime[i] == 0 ) {
              ++t; x /= prime[i];
         }
         if( t ) p[++p[0] ] = prime[i], cnt[++cnt[0] ] = t;
     }
     if(x > 1) p[++p[0] ] = x, cnt[++cnt[0] ] = 1;
// 此時x必定是個素數。 因為在sqrt(x)內都沒有找到其因子 };

此時x的因子個數=(cnt[1]+1)*(cnt[2]+1)*(cnt[3]+1)*(cnt[4]+1)....

額,只知道因子個數而已。不是很好玩ing。能不能求出x的所有約數呢? 恭喜你,這是可以滴。

利用乘法原理對約數集合乘以一個約數得到新的集合,

int divisor[1500];

void getDivisor( int x ) { // 或得x的所有因子
     getPrimeDivisor(x); // 對x進行素因子分解
     divisor[0] = 1; // 存取因子個數
     divisor[1] = 1;
     for ( int i=1; i<=p[0]; ++i ) {
         int nowNum = divisor[0];
         int base = 1;
         for ( int j=1; j<=cnt[i]; ++j )
         {
             base *= p[i];
             for ( int k=1; k<=divisor[0]; ++k )
               divisor[++nowNum ] = divisor[k]*base;
         }
         divisor[0] = nowNum;
     }
}

 

下面通過一道題來測試代碼的正確性:

hdoj 4432 http://acm.hdu.edu.cn/showproblem.php?pid=4432

題意是求n的所有約數,並把每個約數轉化為m進制,把每個進制為的平方加起來。再把最后的結果轉化為m進制。簡單吧。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int prime[3438];
bool isPrime[40007];

void getPrime( int x ){
   for ( int i=1; i<x; i+=2 )
      isPrime[i] = 1, isPrime[i-1] = 0;
   prime[prime[0]=1 ] = 2;
   for ( int i=3; ; i+=2 ) if(isPrime[i] ) {
       int j = i*i, k = i+i;
       if(j >= x) break;
       while(j < x ) {
           isPrime[j] = 0;  j += k;
       }
   }
   for ( int i=3; i<x; i+=2 )
     if(isPrime[i]) prime[++prime[0] ] = i;
}

int p[3438], cnt[3438];

void getPrimeDivisor( int x ) {
     p[0] = cnt[0] = 0; int t;
     for ( int i=1; prime[i]*prime[i]<=x  && i<=prime[0]; ++i ) {
         t = 0;
         while( x%prime[i] == 0 ) {
              ++t; x /= prime[i];
         }
         if(t ) p[++p[0] ] = prime[i], cnt[++cnt[0] ] = t;
     }
     if(x > 1) p[++p[0] ] = x, cnt[++cnt[0] ] = 1;
};

int divisor[1500];

void getDivisor( int x ) {
     getPrimeDivisor(x);
     divisor[0] = 1;
     divisor[1] = 1;
     for ( int i=1; i<=p[0]; ++i ) {
         int nowNum = divisor[0];
         int base = 1;
         for ( int j=1; j<=cnt[i]; ++j )
         {
             base *= p[i];
             for ( int k=1; k<=divisor[0]; ++k )
               divisor[++nowNum ] = divisor[k]*base;
         }
         divisor[0] = nowNum;
     }
}

int get( int x, int bit ){
    int ret = 0;
    while(x ) {
        ret += (x%bit)*(x%bit);
        x /= bit;
    }
    return ret;
};

void print( int x, int bit ) {
    int a[100]={0};
    while( x ) {
       a[++a[0] ] = x%bit;
       x /= bit;
    }
    for ( int i=a[0]; i>0; --i)
      if(a[i] > 9) printf("%c", a[i]-10+'A');
      else printf("%d", a[i]); puts("");
}

int main(){
    getPrime(32000);
    int n, m;
    while( ~scanf("%d%d", &n, &m) ) {
         getDivisor(n);
         int ans = 0;
         for ( int i=1; i<=divisor[0]; ++i ) {
             ans += get(divisor[i], m);
         }
         print(ans, m);
    }
}

 

 

關於素數的運用還有很多的,比如求數x的所有約數的和。初等數論還是挺有意思的,可惜沒時間整了,悲劇啊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM