A. XTUOJ 1339 Interprime
筛法,前缀和
-
素数筛法用于求解满足一定条件的素数个数问题,基础且常用的有两种:埃氏筛和欧拉筛,模板如下:
/* 埃氏筛 */ bool notPrime[MAX_NUM + 1] = { false, true}; for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) { if (!notPrime[i]) { ++cntOfPrimes; if ((long long)i * i > MAX_NUM) continue; // i 的 2 ~ (i - 1) 倍已经被之前的 i 给筛掉了 for (int j = i * i; j <= MAX_NUM; j += i) notPrime[j] = true; } }
/* 欧拉筛 */ int prime[MAX_CNT_OF_PRIMES]; bool notPrime[MAX_NUM + 1] = { false, true }; for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) { if (!notPrime[i]) prime[cntOfPrimes++] = i; for (int j = 0; j < cntOfPrimes; ++j) { if (i * prime[j] > MAX_NUM) break; notPrime[i * prime[j]] = true; // i 能整除当前的 prime[j] 说明此循环内后面的 i * prime[j] 总会被当前这个 prime[j] 筛掉, // 也许在之前就筛掉了,也许会在之后,所以无需在这里重复筛, // 这里退出即可保证每个合数都只被筛一次。 // 比如:当 i == 6,prime == { 2, 3, 5 } 时,在确认 6 % 2 == 0 后退出, // 就会避免筛 6 * 3 == 18,18 会在后面被 9 * 2 == 18 筛掉 if (i % prime[j] == 0) break; } }
-
对于本题,只需要在筛法的基础上求一下所谓的 “内部素数”,最后用前缀和处理一下结果即可
核心代码
void A_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) {
prime[cntOfPrimes] = i;
if (i > 3) {
int avg = (prime[cntOfPrimes - 1] + i) / 2;
if (notPrime[avg]) cntOfInternalPrime[avg] = 1;
}
++order;
}
for (int j = 0; j < cntOfPrimes; ++j) {
if (i * prime[j] > MAX_NUM) break;
notPrime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
for (int i = 2; i <= MAX_NUM; ++i)
cntOfInternalPrime[i] += cntOfInternalPrime[i - 1];
}
B. XTUOJ 1450 哈希
筛法
- 先用筛法打出素数表,再依次枚举素数(由鸽巢原理,小于 n 2 \dfrac{n}{2} 2n 的可以跳过),检查碰撞情况,找到最小不会导致冲突多次的素数即为结果
多次尝试提交后发现最大只需求至 6000 左右的素数
核心代码
void get_prime_table () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_SIZE; ++i) {
if (!notPrime[i]) prime[cntOfPrimes++] = i;
for (int j = 0; j < cntOfPrimes; ++j) {
if (i * prime[j] > MAX_NUM) break;
notPrime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
}
int B_1011_solve () {
for (int i = 0; prime[i]; ++i) {
if (prime[i] < n / 2) continue;
memset(cnt, 0, sizeof(cnt));
for (int j = 0; j < n; ++j) if (++cnt[A[j] % prime[i]] > 2) goto next;
return prime[i];
next: ;
}
}
C. XTUOJ 1279 Dual Prime
筛法,前缀和
- 打出素数表后按题意求 “双素数” ,最后用前缀和处理一下结果即可
核心代码
void C_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) prime[cntOfPrimes++] = i;
for (int j = 0; j < cntOfPrimes; ++j) {
if (i * prime[j] > MAX_NUM) break;
notPrime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
for (int i = 0; prime[i]; ++i) {
for (int j = i + 1; prime[j]; ++j) {
if ((long long)prime[i] * prime[j] > MAX_NUM * 2) break;
cntOfDualPrimes[prime[i] * prime[j]] = 1;
}
}
for (int i = 2; i <= MAX_NUM * 2; ++i)
cntOfDualPrimes[i] += cntOfDualPrimes[i - 1];
}
D. XTUOJ 1355 Euler’s Totient Function
筛法,数论函数,前缀和
- 在欧拉筛法中,每一个合数
n
n
n 都以
n
p
1
∗
p
1
\dfrac{n}{p_1} * p_1
p1n∗p1 的形式被筛去
- 若 p 1 ∣ n p 1 p_1 \mid \dfrac{n}{p_1} p1∣p1n, φ ( n ) = n × ∏ i = 1 k p i − 1 p i = p 1 × n p 1 × ∏ i = 1 k p i − 1 p i = n × φ ( n p 1 ) \varphi(n) = n \times \prod_{i = 1}^k\dfrac{p_i - 1}{p_i} = p_1 \times \dfrac{n}{p_1} \times \prod_{i = 1}^k\dfrac{p_i - 1}{p_i} = n \times \varphi(\dfrac{n}{p_1}) φ(n)=n×∏i=1kpipi−1=p1×p1n×∏i=1kpipi−1=n×φ(p1n)
- 若 p 1 ∤ n p 1 p_1 \nmid \dfrac{n}{p_1} p1∤p1n,此时 p 1 p_1 p1 和 n p 1 \dfrac{n}{p_1} p1n 互质,由欧拉函数是积性函数的性质有: φ ( n ) = φ ( p 1 ) × φ ( n p 1 ) = ( p 1 − 1 ) × n p 1 \varphi(n) = \varphi(p_1) \times \varphi(\dfrac{n}{p_1}) = (p_1 - 1) \times \dfrac{n}{p_1} φ(n)=φ(p1)×φ(p1n)=(p1−1)×p1n
- 最后用前缀和处理一下结果即可
核心代码
void D_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) {
prime[cntOfPrimes++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < cntOfPrimes; ++j) {
int x = i * prime[j];
if (x > MAX_NUM) break;
notPrime[x] = true;
if (i % prime[j] == 0) {
phi[x] = phi[i] * prime[j];
break;
}
phi[x] = phi[i] * phi[prime[j]];
}
}
for (int i = 2; i <= MAX_NUM; ++i)
sumOfPhi[i] = phi[i] + sumOfPhi[i - 1];
}
E. XTUOJ 1377 Factorization
筛法,数论函数,前缀和
- 素数的质因子就是自己,因此质因子数为 1 1 1
- 筛合数
i * prime[j]
时,可以利用前面已经计算出来的i
的质因子数,如果i
能整除prime[j]
,说明i * prime[j]
的质因子数和i
相等,不能整除就说明i * prime[j]
比i
多一个质因子prime[j]
- 最后用前缀和处理一下结果即可
核心代码
void E_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) {
cntOfPrimeFactors[i] = 1;
prime[cntOfPrimes++] = i;
}
for (int j = 0; j < cntOfPrimes; ++j) {
int x = i * prime[j];
if (x > MAX_NUM) break;
notPrime[x] = true;
cntOfPrimeFactors[x] = cntOfPrimeFactors[i] + (i % prime[j] != 0);
if (i % prime[j] == 0) break;
}
}
for (int i = 2; i <= MAX_NUM; ++i)
cntOfPrimeFactors[i] += cntOfPrimeFactors[i - 1];
}
F. XTUOJ 1396 函数
筛法,数论函数,前缀和
- 由整数唯一分解很容易就可以知道这是一个加性函数,结合线性筛的特点很容易就可以求出来,最后用前缀和处理一下结果即可
核心代码
void F_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) {
sumOfExponets[i] = 1;
prime[cntOfPrimes++] = i;
}
for (int j = 0; j < cntOfPrimes; ++j) {
int x = i * prime[j];
if (x > MAX_NUM) break;
notPrime[x] = true;
sumOfExponets[x] = sumOfExponets[i] + sumOfExponets[prime[j]];
if (i % prime[j] == 0) break;
}
}
for (int i = 2; i <= MAX_NUM; ++i)
sumOfExponets[i] += sumOfExponets[i - 1];
}
G. XTUOJ 1402 平方数及其倍数
筛法,前缀和
- 我们将满足 “非 1 1 1 完全平方数或其倍数 ” 这一条件的数称为目标数
- 易知素数不可能是目标数,对于合数,若为目标数,由整数唯一分解可知其必然是素数的平方的倍数
- 结合线性筛的特点,每个数 n n n 都会被 n p 1 ∗ p 1 \dfrac{n}{p_1} * p_1 p1n∗p1 筛去, p 1 p_1 p1 是 n n n 的最小质因子,因此若 n p 1 \dfrac{n}{p_1} p1n 是目标数或 p 1 ∣ n p 1 p_1 \mid \dfrac{n}{p_1} p1∣p1n, n n n 也必然是目标数,反之若 n p 1 \dfrac{n}{p_1} p1n 不是目标数, n n n 也不可能是目标数
- 最后用前缀和处理一下结果即可
核心代码
void G_1011_solve () {
memset(cntOfTargetNums, 0, sizeof(cntOfTargetNums));
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) prime[cntOfPrimes++] = i;
for (int j = 0; j < cntOfPrimes; ++j) {
int x = i * prime[j];
if (x > MAX_NUM) break;
notPrime[x] = true;
if (cntOfTargetNums[i] || i % prime[j] == 0) cntOfTargetNums[x] = 1;
if (i % prime[j] == 0) break;
}
}
for (int i = 2; i <= MAX_NUM; ++i)
cntOfTargetNums[i] += cntOfTargetNums[i - 1];
}
H. XTUOJ 1526 奇因数
筛法,前缀和
- 在 XTUOJ 1377 Factorization 的基础上判断一下是质因子个数否为奇数即可
核心代码
void G_1011_solve () {
for (int i = 2, cntOfPrimes = 0; i <= MAX_NUM; ++i) {
if (!notPrime[i]) {
cntOfTargetNums[i] = 1;
prime[cntOfPrimes++] = i;
}
for (int j = 0; j < cntOfPrimes; ++j) {
int x = i * prime[j];
if (x > MAX_NUM) break;
notPrime[x] = true;
cntOfTargetNums[x] = cntOfTargetNums[i] + (i % prime[j] != 0);
if (i % prime[j] == 0) break;
}
}
for (int i = 2; i <= MAX_NUM; ++i) {
cntOfTargetNums[i] = cntOfTargetNums[i] & 1;
cntOfTargetNums[i] += cntOfTargetNums[i - 1];
}
}