如何快速求出區間范圍[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進制。簡單吧。

#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的所有約數的和。初等數論還是挺有意思的,可惜沒時間整了,悲劇啊。