📜  [L,R]范围内的欧拉Totient函数的概率可以被M整除(1)

📅  最后修改于: 2023-12-03 14:59:08.116000             🧑  作者: Mango

[L,R]范围内的欧拉Totient函数的概率可以被M整除

什么是欧拉Totient函数?

欧拉Totient函数,也叫欧拉 phi 函数,表示小于等于n且与n互质的正整数个数,记作 $\varphi(n)$。例如,$\varphi(6)=2$,因为小于等于6的正整数且与6互质的只有1和5。

问题描述

给定两个正整数L和R,以及一个正整数M,求[L,R]范围内的欧拉Totient值可以被M整除的数的个数除以R-L+1。即:$\frac{|{i \in [L,R]: \varphi(i) \equiv 0 \pmod M}|}{R-L+1}$。

解法
1. 暴力枚举

最基础的方法就是枚举[L,R]范围内的每一个数i,统计其欧拉Totient值是否可以被M整除。时间复杂度约为 $O(R(\log R + \sqrt R))$,不是特别高效。

int cnt = 0;
for(int i=L; i<=R; i++) {
    if(phi(i) % M == 0) cnt++;
}
double ans = 1.0 * cnt / (R-L+1);
2. 容斥原理

由欧拉Totient函数的性质得:$\varphi(p_1p_2\cdots p_k) = (p_1-1)(p_2-1)\cdots(p_k-1)p_1^{a_1-1}p_2^{a_2-1}\cdots p_k^{a_k-1}$。其中,$p_1,p_2,\cdots,p_k$ 为质数,$a_1,a_2,\cdots,a_k$ 是对应质数的次数。

考虑使用容斥原理,将所有[L,R]范围内质因子全为1至少有1个的数去掉。设集合$S_i$表示[L,R]范围内所有质因子均为$p_i$的倍数的数的集合,则用容斥原理可以得到:

$$ \left|\bigcap\limits_{i=1}^{k}\bar{S_i}\right| = \sum\limits_{\varnothing \neq I \subseteq {1,2,\cdots,k}}(-1)^{|I|+1}\left|\bigcap_{i\in I}S_i\right| $$

其中,$\varnothing$ 是集合空集,$|I|$ 表示集合 $I$ 的元素个数。根据上述欧拉Totient函数的性质,能得到:

$$\varphi(m) \bmod M = 0 \iff \prod\limits_{i=1}^{k}{(p_i-1) \bmod M} \times (p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k}) \bmod M = 0$$

因此,可以对所有 $(p_1-1),(p_2-1),\cdots,(p_k-1),(p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k})$ 分别建立集合 $S_i$,即可求出期望答案。

时间复杂度约为 $O(\pi(\sqrt{R}) \log M)$,其中 $\pi(x)$ 表示小于等于 $x$ 的质数个数。

int cnt[MAXN], p[MAXN], mu[MAXN], phi[MAXN], fac[MAXN], tot;
bool vis[MAXN];
void init() {
    mu[1] = phi[1] = fac[0] = fac[1] = 1;
    for(int i=2; i<MAXN; i++) {
        if(!vis[i]) {
            p[++tot] = i;
            mu[i] = -1;
            phi[i] = i-1;
        }
        for(int j=1; j<=tot && i*p[j]<MAXN; j++) {
            vis[i*p[j]] = true;
            if(i % p[j] == 0) {
                mu[i*p[j]] = 0;
                phi[i*p[j]] = phi[i] * p[j];
                break;
            }
            mu[i*p[j]] = -mu[i];
            phi[i*p[j]] = phi[i] * (p[j]-1);
        }
        fac[i] = (1LL * fac[i-1] * i) % MOD;
    }
}
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); }

double solve(int L, int R, int M) {
    memset(cnt, 0, sizeof cnt);
    int n = R-L+1;
    for(int k=1; k<=tot; k++) {
        int x = p[k];
        for(int i = ((L-1)/x+1)*x; i<=R; i+=x) {
            cnt[i-L+1]++;
        }
    }
    for(int i=L; i<=R; i++) {
        if(i > 1) cnt[i-L+1] += cnt[i-L];
    }
    double ans = 0.0;
    for(int k=1; k<=tot; k++) {
        int x = p[k], mul = 1, sgn = ((tot-k) & 1) ? -1 : 1;
        while(mul * x <= M) mul *= x;
        for(int i = ((L-1)/x+1)*x; i<=R; i+=x) {
            int l = i-L+1, r = min(R-L+1, i+mul-L);
            ans += sgn * (((fac[n-(r-l+1)] * fac[r-l+1] % MOD) * mul % MOD) / fac[r-l+1]);
        }
    }
    return ans / n;
}
参考文献
  • 《挑战程序设计竞赛》
  • luogu P4745 [SHOI2014]Totient Function 斯特林数