Problem
詢問一個區間有多少 鄒賽爾數 (Zeisel number)
鄒賽爾數是一種無平方數因數的數,而且至少有三個質因數可以用下式表示,質因數由小排到大滿足$p_{x} = A p_{x-1} + B$。
105, 1419, 1729, 1885, 4505, 5719, 15387, 24211, 25085, …
1 2 3 4 5 6 7
| 4505 = 1 * 5 * 17 * 53 A = 3, B = 2 p0 = 1 p1 = 3 * p0 + 2 = 5 p2 = 3 * p1 + 2 = 17 p3 = 3 * p2 + 2 = 53
|
找到 $10^{15}$ 以內的所有 Zeisel number。
Sample Output
Solution
一開始設想,既然小於 $10^{15}$ 又要三個質數,那麼最小的質因數應該要小於 $10^5$,否則會超過指定範圍。
接著第一次嘗試時,窮舉變數 $A$ 針對第一個質因數$p_{1}$ 得到$B = p_{1} - A$,得到下一項在範圍內 … 這樣效率非常差。A 的範圍非常大,質因數也非常多,而且還要檢驗每一項是否質數,效能相當低落。
接著第二次嘗試時,窮舉$p_{1}, p_{2}$,這樣的計算量是 $10^5$ 內的質數個數 n,至少為 $O(n^2)$,嘗試一下發現速度還是太慢,藉由聯立方程式解出 A, B,卻有不少 A 是不存在的整數解。
1 2 3
| A + B = p1 p1 A + B = p2 A = (p2 - p1)/(p1 - 1), B = p1 - A
|
接著第三次嘗試時,反過來窮舉,先讓 $A$ 一定有解,再看$p_{2}$ 是否為質數,因此$p_2 = p_1 + k \times (p_1 - 1)$,由於$p_{i} - 1$ 造成的增長幅度遠比質數個數快很多,效能上有顯著地提升。
現在還有一個疑問,保證$p_1 \le 10^5$,但是當$p_1$ 很小$p_2$ 可能會大於 $10^5$ 嗎,甚至必須窮舉到 $\sqrt{10^{15}}$?
這裡我不確定,放大範圍去嘗試時,發現找到的 Zeisel number 最多 9607 個,放大搜索範圍不再增加,那就不斷地縮減,讓找到的時間縮小不逾時。
不是說好限制 2.000s?1.980s 得到 TLE,第一次使用 NTUJ 真歡樂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include <stdio.h> #include <string.h> #include <math.h> #include <algorithm> #include <queue> using namespace std; #define MAXL (10000000>>5)+1 #define GET(x) (mark[x>>5]>>(x&31)&1) #define SET(x) (mark[x>>5] |= 1<<(x&31)) int mark[MAXL]; int P[5500000], Pt = 0; vector<long long> ret; void sieve() { register int i, j, k; SET(1); int n = 10000000; for(i = 2; i <= n; i++) { if(!GET(i)) { for (k = n/i, j = i*k; k >= i; k--, j -= i) SET(j); P[Pt++] = i; } } } long long MAXVAL = 1e+15; int isprime(long long v) { if (v < 2) return 0; if (v < 10000000) return !GET(v); for (int i = 0; i < Pt && (long long) P[i] * P[i] <= v; i++) if (v%P[i] == 0) return 0; return 1; } void make() { for (int i = 0; i < Pt && P[i] < 100000; i++) { for (int j = P[i] + P[i]-1; j < 1000000; j += P[i] - 1) { if (!isprime(j)) continue; long long A = (j - P[i])/(P[i]-1); long long B = P[i] - A; long long m = P[i], cnt = 1, mm = P[i]; while (mm <= MAXVAL) { if (A && MAXVAL / m / A <= 0) break; if (m * A + B <= m) break; m = m * A + B; if (MAXVAL / mm / m <= 0) break; if (!isprime(m)) break; mm = mm * m, cnt ++; if (cnt >= 3) { ret.push_back(mm); } } } } sort(ret.begin(), ret.end()); for (int i = 0; i < 243; i++) printf("%d\n", ret[i]); } int main() { sieve(); make(); int testcase; long long L, R; scanf("%d", &testcase); while (testcase--) { scanf("%lld %lld", &L, &R); int r = 0; for (int i = 0; i < ret.size(); i++) r += ret[i] >= L && ret[i] <= R; printf("%d\n", r); } return 0; }
|