1. 程式人生 > >素數處理-艾式篩法及其位優化版-hdoj1397

素數處理-艾式篩法及其位優化版-hdoj1397

這篇文章介紹了素數處理的常用方法-艾式篩法及其位優化版本.

目錄

1.埃式篩法是什麼?

首先埃式篩法是一種演算法,核心是利用打表法高效獲取0到n內的素數,模版型演算法.

2.埃式篩法有什麼用?

一般用於解決這種問題:

   素數相關的問題.

標誌性經典問題:

    現給你一個正整數n(n<1000000),問0到n內的素數個數為多少?

想這種素數問題資料小時可以用n*n解,可演算法嘛,就是用來在大資料下體現高效的,這裡就需要用到埃式篩法.

3.埃式篩法實現思路

    處理素數之前我們先了解一下什麼是素數:

    質數(prime number)又稱素數,表示一個大於1的自然數,除了1和它本身外,不能被其他自然數整除.

    故而我們知道了,如果一個數能被除了1和它本身的數整除,那它肯定不是素數,故而很早以前有人研究出了埃式篩法,先定義一個prime陣列用於存放找到的素數,標誌陣列falg標誌每個數是不是素數,最開始除了0和1意外0到n每個數都是素數,我們知道0,1不是素數,2是素數.故而我們從2開始,

        先把2存進prime數組裡,然後將後面小於等於n又是2的倍數的數字全部標記為不是素數,

        然後向後找到第一個未被標記不是素數的數字3,因為未被標記故而小於3的數字都不是3的因子(從2開始),故而是3素數

        將3放進prime陣列,然後將後面小於等於n又是3的倍數的數字全部標記為不是素數,

        然後向後找到第一個未被標記不是素數的數字5,

        .......

    最後到n時,篩選完畢,0到n內是素數的數都在prime陣列中存好了.標誌每個數是不是陣列的flag陣列也做好了,i是素數則flag[i]為true,這就是埃式篩法的思路,如同其名,篩法.

4.模版程式碼

先是普通的打表

 //埃氏篩法 普通打表
#include <stdio.h> 
const int maxn=100000;
int prime[maxn],primenum;
bool flag[maxn];
void common_prime(int t){ //prime陣列和flag陣列都做好
	primenum=0; 
	flag[0]=flag[1]=false;
	for(int k=2;k<=t;k++) flag[k]=true;
	for(int i=2;i<=t;i++){
		if(flag[i]){
			for(int j=i<<1;j<=t;j+=i) flag[j]=false;
			prime[primenum++]=i; 
		}
	}
}

void common_prime_onlyflag(int t){ //只做flag陣列
	flag[0]=flag[1]=false;
	for(int k=2;k<=t;k++) flag[k]=true;
	for(int i=2;i<=t;i++) if(flag[i]) for(int j=i<<1;j<=t;j+=i) flag[j]=false; 
}

int main(){
	int t=100;
	common_prime(t);
	for(int i=0;i<primenum;i++) printf("%d ",prime[i]);
	printf("\n");
	for(int k=0;k<=t;k++) if(flag[k])  printf("%d ",k);
	return 0;
}

然後我們介紹一種更節約空間的方法:位壓縮法

bool型佔1個位元組8個位,其實我們只需要一個位就能表示真與假,我們可以用一個int,用他的32個位分別表示32個數是否是素數,這樣我們相比與一個bool表示一個數的真假狀態能縮小8倍空間!

原始碼如下,最關鍵的是將位置i對映到對應的第i/32個數字的第i%32個二進位制位,其它的與普通埃式篩法完全相同

// 位操作求素數 
#include <stdio.h>

const int maxn=1000000;
int prime[maxn],primenum;
int flag[maxn/32+1];//陣列大小實際縮小8倍 
void wei_prime(int t){
	primenum=0;
	flag[0]|=3;
	for(int k=1;k<=t/32+1;k++) flag[k]=0;
	for(int i=2;i<=t;i++){
		if(!((flag[i/32]>>(i%32))&1)){
			for(int j=i*2;j<=t;j+=i) flag[j/32]|=(1<<(j%32));
			prime[primenum++]=i;
		}	
	}
}
void wei_prime_onlyflag(int t){
	flag[0]|=3;
	for(int k=1;k<=t/32+1;k++) flag[k]=0;
	for(int i=2;i<=t;i++) if(!((flag[i/32]>>(i%32))&1)) for(int j=i*2;j<=t;j+=i) flag[j/32]|=(1<<(j%32));
}
void printby_prime(){
	for(int i=0;i<primenum;i++) printf("%d ",prime[i]);
}
void printby_flag(int t){
	for(int i=0;i<=t;i++) if(!((flag[i/32]>>(i%32))&1)) printf("%d ",i);
}

int main(){
	int t=100;
	wei_prime(t);
	printby_prime();
	printf("\n");
	printby_flag(t);
	return 0;
}

5.例題訓練

Problem Description

    Goldbach's Conjecture: For any even number n greater than or equal to 4, there exists at least one pair of prime numbers p1 and p2 such that n = p1 + p2.
This conjecture has not been proved nor refused yet. No one is sure whether this conjecture actually holds. However, one can find such a pair of prime numbers, if any, for a given even number. The problem here is to write a program that reports the number of all the pairs of prime numbers satisfying the condition in the conjecture for a given even number.
    A sequence of even numbers is given as input. Corresponding to each number, the program should output the number of pairs mentioned above. Notice that we are interested in the number of essentially different pairs and therefore you should not count (p1, p2) and (p2, p1) separately as two different pairs.

Input

An integer is given in each input line. You may assume that each integer is even, and is greater than or equal to 4 and less than 2^15. The end of the input is indicated by a number 0.

Output

Each output line should contain an integer number. No other characters should appear in the output.

Sample Input

610120

Sample Output

121

題意分析:

    其實就是要你求兩素數之和為n的組合數,因為n最大會達到2的15次方即32768,n*n的暴力求解剛好爆掉,故而需要使用埃式篩法,先預處理出0到32768的所有素數是否為素數的flag陣列,在對每個輸入的n做一次0到n/2的迴圈判斷即可.

ac程式碼:

#include <stdio.h> 
const int maxn=100000;
bool flag[maxn];

void common_prime_onlyflag(int t){
	flag[0]=flag[1]=false;
	for(int k=2;k<=t;k++) flag[k]=true;
	for(int i=2;i<=t;i++) if(flag[i]) for(int j=i<<1;j<=t;j+=i) flag[j]=false; 
}

int main(){
	int t=1;
	for(int i=0;i<15;i++) t*=2;
	common_prime_onlyflag(t);
	int num;
	while(1){
		scanf("%d",&num);
		if(num==0) break;
		int ans=0;
		for(int i=2;i<=num/2;i++) if(flag[i]&&flag[num-i]) ans++; 
		printf("%d\n",ans);
	}
	return 0;
}

2.待新增高難度題