SPOJ Prime Generator [PRIME1]
https://www.spoj.pl/problems/PRIME1/
首先想到的肯定是全部素数打表出来,但是1e9的规模,显然是TLE的。
于是,之后整个下午学习了Miller-Rabin素数测试法。
方法1:对于[a, b]内的每个数,进行MR判定,素数输出。特别注意的地方,由于MR是基于概率的,所以通常要多试几次,我尝试过只能在T>=3次的情况下AC,T小于3次,都不行。(据无来源消息,重复T次,出错的概率是2^(-T),所以……)
代码如下:
#include <stdio.h> #include <iostream> #include <stdlib.h> #include <time.h> using namespace std; typedef long long LL; LL PowerMod(LL a, LL b, LL n) { if (b == 1) return a % n; LL x = PowerMod(a, b >> 1, n); x = x * x % n; if (b & 1) x = x * a % n; return x; } bool Witness(LL a, LL n) { LL m = n - 1; int q = 0; while((m&1) == 0) { q ++; m >>= 1; } LL x = PowerMod(a, m, n); if (x == 1 || x == n-1) return false; while(q --) { x = x * x % n; if (x == n-1) return false; } return true; } bool MillerRabin(int n, int T = 3) { if (n < 2) return false; if (n == 2) return true; if ((n&1) == 0) return false; while(T --) { int a = rand() % (n-2) + 1; if (Witness(a, n)) return false; } return true; } int main() { srand(unsigned(time(NULL))); int t; scanf("%d", &t); for (int i = 0; i < t; i ++) { int a, b; scanf("%d%d", &a, &b); i == 0 ? : puts(""); while(a <= b) { if (MillerRabin(a)) { printf("%d\n", a); } a ++; } } return 0; }
代码跑出来之后,看下时间是1.80s,很慢。看看其他,都是1s内的,于是Google其他方法。
找到这个:http://www.algorithmist.com/index.php/SPOJ_PRIME1#Explanation
里面有解释。我翻译一下:
这题使用的是标准筛法的一个修改版本。如果用普通的筛法,对[2, n]内的每个数进行排查,明显不是高效的,会使用很多的时间和空间。然而,我们可以发现,[0, n]内没有一个合数的因子大于floor(sqrt(n))。所以,我们只需要把[2, sqrt(n)],即[2, 31622]以内的素数筛出来。然后,对于每个询问[a, b],使用预先筛好的素数进行第二次筛选,最后得到[a, b]内的素数,输出。
我开始没有明白最后一句,以为得到[2, sqrt(n)]的素数之后,对于每个询问[a, b]内的数n,只需要暴力测试一下。于是我写了之后,交上去,跑了2s+,比MR还慢。最后再细读Explanation,才明白。修改之后,交上去,跑了0.08ms。
代码如下:
#include <iostream> #include <stdio.h> #include <math.h> #include <string.h> #define M 31625 #define N 100005 using namespace std; bool Prime[M]; int PrimeList[3402]; int numP; bool that[N]; void makePrime() { int p = 2; while(p * p < M) { for (int i = p*p; i < M; i += p) Prime[i] = true; while(Prime[++p]); } for (int i = 2; i < M; i ++) { if (Prime[i] == false) { PrimeList[numP ++] = i; } } // cout << numP << endl; } int main() { makePrime(); int t; scanf("%d", &t); for (int i = 0; i < t; i ++) { i == 0 ? : puts(""); int a, b; scanf("%d%d", &a, &b); if (a == 1) a++; memset(that, 0, sizeof(that)); for (int i = 0; i < numP; i ++) { for (int j = PrimeList[i] * int(a / PrimeList[i]); j <= b; j += PrimeList[i]) { if (j >= a) { that[j - a] = true; // 把[a, b]内的合数筛掉 } } } for (int i = a; i <= b; i ++) { if (that[i-a] == 0) { printf("%d\n", i); } } } return 0; }