XTU OJ 2024 下学期作业 0x0B - 筛法,前缀和

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 p1np1 的形式被筛去
    • p 1 ∣ n p 1 p_1 \mid \dfrac{n}{p_1} p1p1n φ ( 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=1kpipi1=p1×p1n×i=1kpipi1=n×φ(p1n)
    • p 1 ∤ n p 1 p_1 \nmid \dfrac{n}{p_1} p1p1n,此时 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)=(p11)×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 p1np1 筛去, 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} p1p1n 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 奇因数

筛法,前缀和


核心代码

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];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值