780 likes | 915 Views
演算法 期末 Project. 組員 : 99703009 詹伊婷 99703017 孟繁辰 99703036 陳育聖. 題目 ( 判斷質數進階版 ). 題意 : 輸入為多行, 每一行包含兩個大於 1 的整數 a,b , 1 < a ≤ b < 1000000000 (10 9 ) 。 時間限制: 20000 ms 記憶體限制 : 80000 KBytes 輸出 a 與 b 之間 ( 含 ) 有多少個質數 。 來源 : http://judge.nccucs.org/ShowProblem?problemid=d063. 範例輸入、輸出.
E N D
演算法 期末Project 組員 : 99703009 詹伊婷99703017 孟繁辰99703036陳育聖
題目(判斷質數進階版) 題意 : 輸入為多行, 每一行包含兩個大於1的整數a,b, 1 < a ≤ b < 1000000000(109)。 時間限制:20000 ms 記憶體限制 : 80000 KBytes 輸出a與b之間(含)有多少個質數。 來源: http://judge.nccucs.org/ShowProblem?problemid=d063
範例輸入、輸出 範例輸入 : 24 3100 2999999999 範例輸出 : 2 24 50847534
題目分析(何謂質數?) • 何謂質數? 定義: 指在一個大於1的自然數中, 除了1和此整數自身外, 無法被其他自然數整除的數。
由定義判斷質數 boolIsPrime (int num) { for (int n = 2; n < num; ++n) { if(num % n == 0) return false; } return true; } /*題目中保證 num > 1 */
時間複雜度 for (int n = 2; n < num; ++n) { 略 } 可得知,檢查 1 個數的時間複雜度為 (若輸入的數字為 k) 需要 O(k-2) = O(k)
pseudocode Get a, b num =0 For n = a to b// O(b - a) IF( IsPrime(n)) // O( n ) num = num + 1 // O( 1 ) => Answer is num
整體複雜度 時間複雜度分析 : 假設輸入 1, n,所需要的複雜度為 O(1 + 2 + 3 + … + (n-1) + n) =O((n+1)*(n)/2) = O(n2) ============================================== 假設輸入 a, b,所需要的複雜度為 O(a + (a+1) + (a+2) + … + (b-1) + b) =O((a + b)*(b – a + 1)/2) = O(b2 - a2) 空間複雜度分析 :O(1) (題目要求的是範圍內)
執行結果(TLE) 檢測所需時間 實際操作 (n最大值為109-1) for(i=0; i<109; ++i);/*耗時2.8秒*/ 所以若用以上的方法 1 ~ 109檢測是否為質數所需時間為 2.8 * 109s => 約 90年 題目時間限制 :20秒
思考! 思考
思考 =>分析 分析1: 對稱 8 = 2*4 = 4*2 9 = 3*3 10 = 2*5 = 5*2 12 = 2*6 = 3*4 = 4*3 = 6*2 n = m0*m1 (m0 <= m1) 發現只需要檢查到 m0最大值 n = m*m 所以 檢查到 sqrt(n) 即可
思考 =>分析 分析2:歸納 是 2的倍數 => 不是質數 是 3 的倍數 => 不是質數 是 4 的倍數 => 不是質數 (4 = 2*2) (所以 4 不是質數,所以也是質數的倍數) 是 5 的倍數 => 不是質數 是 質數的倍數 => 不是質數 所以只需判斷 n 是否能整除質數
思考結果 結合分析1、2,只需檢查 小於sqrt(n)的所有質數是否能被整除 sqrt(109) = 31622 所以先產生所有小於31622的質數, 放置在陣列中,判斷是否為質數時, 取用所有小於sqrt(n)的值, 若都不可整除則為 質數
/* p[] 為小於31622的 質數陣列 由小排到大 */ /* p.length = 3401 */ boolIsPrime (int num) { inti = 0; for(; i<p.length && p[i]*p[i]<=num; ++i) if(num % p[i] == 0) return false; return true; }
時間複雜度 檢查一個數字 n 所需要的時間複雜度 將會是小於等於 sqrt(n) 以下的質數個數 這個問題牽涉到質數的分布 根據質數分布定理(花了一百年才得證) n以下的質數個數 = π(n), 近似值 = n/(log n) ( π(n)=n / (log n), when n = ∞) 所以檢查 1 個數字所需 O(n0.5/log n)
由上圖 可以觀察得到: (1)當x越來越大時,π(x)/x 越來越接近 0。 (2)當x越來越大時,π(x)log(x)/x 越來越接近 1。
空間複雜度 若需要判斷的數最大值為 n ,就必須產生 小於等於sqrt(n)所有質數個數的陣列存放 O(小於等於 sqrt(n)的所有質數個數) =O(π(n0.5)) = O( n0.5/log(n) )
pseudocode Get a, b (max num = m) Make Prime Table// O(m1/2*m1/4 = m3/4) (建立m1/2以下質數陣列,判斷1個數x需O(x1/2)) num =0 For n = a to b// O(b - a) IF(IsPrime(n))// O(π(n0.5)) num = num + 1 // O( 1 ) => Answer is num
整體複雜度 時間複雜度分析 : 輸入 a, b O(m3/4 + (b – a) * π(n0.5)) =O(m3/4 + b * b0.5/log(b)) = O(m3/4 + b1.5/log(b)) 空間複雜度分析 :(max num = n) O(小於等於 sqrt(n) 的所有質數個數) =O(π(n0.5)) = O( n0.5/log(n) )
執行結果(TLE) 實際操作 1 ~ 109檢測 耗費時間 : 13分鐘 90 年 => 13 分鐘 進步惹!!! 題目時間限制 :20秒
思考 最初 : for(i=0; i<109; ++i);/*耗時2.8秒*/ 題目時間限制 :20秒 所以時間複雜度必須壓制在 O(n) 最為保險 也就是說,輸入一個數 我必須在 O(1) 判斷出是否為質數
發現提示 題目提示 : Sieve of Eratosthenes 建出完全的質數表 太好惹~~~有提示欸~~~
Sieve of Eratosthenes 這是一個製作質數表的方法,通常簡稱為「篩法」。 列出所有正整數 從 2 開始,刪掉 2 的倍數 找下一個未被刪掉的數字,找到3,刪掉 3 的倍數 找下一個未被刪掉的數字,找到5,刪掉 5 的倍數 如此不斷下去,就能刪掉所有合數,找到所有質數。 http://www.csie.ntnu.edu.tw/~u91029/Prime.html
結合分析 IsPrime[num] = { true }; for(i=2; i<num; ++i) { if(IsPrime[i]) { for(j=2*i; j<num; j+=i) { IsPrime[j] = false; } } } 由前面歸納的重點可修改成更快的方式
for(i=2; i<sqrt_num; ++i) { if(IsPrime[i]) { for(j=i*i; j<num; j+=i) { IsPrime[j] = false; } } } 欲刪掉質數 i 的倍數之時, 早已刪掉 1 倍到 i-1 倍了, 所以直接從 i倍開始。
const int MAX = 109; boolPrimeTable[MAX]; /* 全域陣列初始化為false */ void MakePrimeTable() { for(int A=2; A<SQRT_MAX; ++A) if(PT[ A ] == false) for(int B=A*A; B<MAX; B+=A) PT[ B ] = true; } 最後存放在PT[]中,false為質數 (除0, 1)
無法執行(MLE) MLE : Memory Limit Exceed 表示程序執行超過記憶體限制 sizeof( bool ) 發現佔 1 個byte 所以共使用了 109 bytes = 976,562.5KB (超大) (1024 bytes = 1KB) 題目記憶體限制 :80,000 KB
解決方法 1.記錄 真與否 只需要 1 bit(1byte = 8bits) (利用unsigned int 4bytes(32 bits)實做) 2.偶數可不用檢測 109 bits = 109 / 2/8 / 1024 = 61035.15625 KB < 80000 KB
bits表示 1 個 unsigned int: 編號:1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 0 0 0 1 0 0 1 0 0 1 0 1 1 數字: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 =============================================== 編號: 15 16 17 18 19 20 21 22 23 24 25 26 27 28 0 0 1 1 0 1 0 0 1 0 1 1 0 1 數字: 29 31 33 35 37 39 41 43 45 47 49 51 53 55 =============================================== 編號: 29 30 31 32 1 0 0 1 數字: 57 59 61 63 ( 0代表 3, 5, 7, 11, 13…… 是質數 )
const int MAX = 1000000000; const int SQRT_MAX = sqrt( MAX ); unsigned int PT[MAX >> 6]; /*全域變數才放得下*/ void MakePrimeTable() { unsigned int A, B; for(A=3; A < SQRT_MAX; A+=2) if((PT[A>>6] & (1<<((A>>1) & 31))) == 0) for(B=A*A; B < MAX; B += (A <<1)) PT[B>>6] |= (1<<((B>>1) & 31)); }
時間複雜度 只有質數會消掉它本身的倍數 所以為 O((n0.5/log n)*(n/m)) n:範圍n n0.5/log n:質數個數 m:質數的值 => n/m: 每個質數需消除的倍數個數 可寫為 O(n1.5/(log(n)*m))
執行結果(TLE) 建表需時 :14.2秒 檢測 1 ~ 109需時 :2 秒左右 但是輸入檔不會只有 1 筆輸入 所以需要更快的方法……
振作 我們在一開始就將 2 的倍數剔除 所以說 我們也可以在一開始就將 3 的倍數剔除 6n + 06n + 1 6n + 26n + 3 6n + 46n + 5 所以只需檢查 6n + 1, 6n + 5 的數
const int MAX = (1000000000 >> 1); const int SQRT_MAX = sqrt( (MAX << 1) ) / 2; unsigned int PT[MAX >> 5]; unsigned int N[2] = { 2, 1 }; /* 6n + 1, 6n + 5 跳 2, 1, 2, 1…… 略過偶數 */ boolIsPrime(int n) { return ((PT[n>>5] & (1<<(n & 31))) == 0); }
void MakePrimeTable() { unsigned inti, j, k = 0, n; for(i=2; i<SQRT_MAX; i += N[(++k)&1]) { if(IsPrime( i )){ n = (i << 1) + 1; j = (n * n) >> 1; n = (n << 1) + 1; for(; j < MAX; j = ((j << 1) + n)>>1) PT[j >> 5] |= (1 << (j & 31)); } } }