UVa11440 Help Tomisu

题目链接

给定正整数 N 和 M ,统计 2 和 N!之间有多少个整数 x 满足: x 的所有素因子都大于 M ( 2 ≤ N ≤ 1 0 7 10^7 107 , 1 ≤ M ≤ N , N-M ≤ 1 0 5 10^5 105 )。输出答案除以 100000007 的余数。例如, N=100 , M=10 时答案为 43274465 。

样例:

Sample Input
100 10
100 20
10000 9000
0 0
Sample Output
43274465
70342844
39714141

因为 M ! = 1 ∗ 2 ∗ 3 ∗ . . . ∗ M M!=1*2*3*...*M M!=123...M,
x 的所有素因子都大于 M ,则 x 与 M ! M! M! 没有相同素因子,即 x 与 M ! M! M! 互素。
此时我们就可以根据欧拉公式求出“小于 M ! M! M! 且与 M ! M! M! 互素的正整数的个数” φ ( M ! ) \varphi(M!) φ(M!)

欧拉公式:
给出正整数 n 的唯一分解式:
n = p 1 a 1 p 2 a 2 . . . p k a k n=p_1^{a_1}p_2^{a_2}...p_k^{a_k} n=p1a1p2a2...pkak
则“小于 n n n 且与 n n n 互素的正整数的个数”为:
φ ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) \varphi(n)=n(1-\frac 1 {p_1})(1-\frac 1 {p_2})...(1-\frac 1 {p_k}) φ(n)=n(1p11)(1p21)...(1pk1)

证明: φ ( k n ) = k φ ( n ) \varphi(kn)=k\varphi(n) φ(kn)=kφ(n)
φ ( n ) \varphi(n) φ(n) 表示“小于 n n n 且与 n n n 互素的正整数的个数” ( 即在 [1,n] 区间内),假设 x 是在 [1,n] 的整数,那么 x 是否与 n 互素取决于 gcd(n,x) ( 最小公倍数)。那么 x+kn 是在 [kn+1,(k+1)n] ,根据辗转相除法, gcd(x+kn,n)=gcd(n,(x+kn)%n)=gcd(n,x),可以看到,如果 gcd(n,x)=1 ( x 与 n 互素),则 x+kn 与 n 也互素。如果 gcd(n,x)>1 ( x 与 n 不互素),则 x+kn 与 n 也不互素。所以每隔 n 个的区间与 n 互素的个数是相同的。

所以 φ ( N ! ) = φ ( M ! ) × N ! M ! = φ ( M ! ) × ( M + 1 ) × ( M + 2 ) × . . . × N \varphi(N!)=\varphi(M!)\times\frac {N!} {M!}=\varphi(M!)\times(M+1)\times(M+2)\times...\times N φ(N!)=φ(M!)×M!N!=φ(M!)×(M+1)×(M+2)×...×N

同时要注意到样例所给的是多组数据,如果我们每组数据都求一次,则时间消耗太大,假设 f ( N ) = φ ( N ! ) f(N)=\varphi(N!) f(N)=φ(N!),我们可以用递推的方法求出所有的 f ( N ) f(N) f(N)

  1. 如果 n 是素数,
    假设 (n-1)!可以唯一分解为:
    ( n − 1 ) ! = p 1 a 1 p 2 a 2 . . . p k a k (n-1)!=p_1^{a_1}p_2^{a_2}...p_k^{a_k} (n1)!=p1a1p2a2...pkak
    则 n!可以唯一分解为:
    n ! = p 1 a 1 p 2 a 2 . . . p k a k n n!=p_1^{a_1}p_2^{a_2}...p_k^{a_k}n n!=p1a1p2a2...pkakn
    则:
    f ( n − 1 ) = ( n − 1 ) ! × ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) f(n-1)=(n-1)!\times(1-\frac 1 {p_1})(1-\frac 1 {p_2})...(1-\frac 1 {p_k}) f(n1)=(n1)!×(1p11)(1p21)...(1pk1)
    f ( n ) = n ! × ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) ( 1 − 1 n ) = f ( n − 1 ) ( n − 1 ) f(n)=n!\times(1-\frac 1 {p_1})(1-\frac 1 {p_2})...(1-\frac 1 {p_k})(1-\frac 1 n)=f(n-1)(n-1) f(n)=n!×(1p11)(1p21)...(1pk1)(1n1)=f(n1)(n1)

  2. 如果 n 不是素数,
    假设 (n-1)!可以唯一分解为:
    ( n − 1 ) ! = p 1 a 1 p 2 a 2 . . . p k a k (n-1)!=p_1^{a_1}p_2^{a_2}...p_k^{a_k} (n1)!=p1a1p2a2...pkak
    则 n!可以唯一分解为:
    n ! = p 1 b 1 p 2 b 2 . . . p k b k n!=p_1^{b_1}p_2^{b_2}...p_k^{b_k} n!=p1b1p2b2...pkbk
    则:
    f ( n − 1 ) = ( n − 1 ) ! × ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) f(n-1)=(n-1)!\times(1-\frac 1 {p_1})(1-\frac 1 {p_2})...(1-\frac 1 {p_k}) f(n1)=(n1)!×(1p11)(1p21)...(1pk1)
    f ( n ) = n ! × ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) = f ( n − 1 ) n f(n)=n!\times(1-\frac 1 {p_1})(1-\frac 1 {p_2})...(1-\frac 1 {p_k})=f(n-1)n f(n)=n!×(1p11)(1p21)...(1pk1)=f(n1)n

如何求 n 是否为素数,效率最高的方法是筛法。
使用筛法求素数要注意,小于 n 的合数的最小素因子最大不可能超过 n \sqrt n n ,因为如果最小素因子超过 n \sqrt n n ,那么最少两个素因子相乘一定大于 n 。所以筛法中只需要遍历 1 到 n \sqrt n n 即可。

可以看到,不论是递推求 f ( n ) f(n) f(n) ,还是求最终结果,都是使用的乘法,题目中说输出答案除以 100000007 的余数,根据余数的性质可以边计算边取余,防止溢出:
a b m o d    n = ( a m o d    n ) ( b m o d    n ) m o d    n ab \mod n=(a\mod n)(b\mod n) \mod n abmodn=(amodn)(bmodn)modn

同时需要注意题目“统计 2 和 N!之间有多少个整数 x 满足条件”,所以最终要去掉 1 。为防止减法使得结果为负数,所以需要减去模: (ans - 1 + MOD) % MOD
完整代码:

//#define LOCAL
#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
#include <cstring>

using namespace std;

const int maxn = 10000000 + 10;
const int MOD = 100000007;

int vis[maxn];    // 记录是否是素数,素数记 0 ,合数记 1
int phifac[maxn]; // 不超过 i!且与 i!互素的整数个数

// 筛法求素数
void get_primes(int n)
{
    int m = (int)sqrt(n + 0.5);
    memset(vis, 0, sizeof(vis));
    for (int i = 2; i <= m; i++)
    {
        //如果 i 是素数
        if (!vis[i])
        {
            for (int j = i * i; j <= n; j += i)
            {
                vis[j] = 1;
            }
        }
    }
}

int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif // LOCAL
    get_primes(10000000);
    phifac[1] = phifac[2] = 1;
    for (int i = 3; i <= 10000000; i++)
    {
        phifac[i] = (long long)phifac[i - 1] * (vis[i] ? i : i - 1) % MOD;
    }

    int n, m;
    while (cin >> n >> m && n && m)
    {
        int ans = phifac[m];
        for (int i = m + 1; i <= n; i++)
        {
            ans = (long long)ans * i % MOD;
        }
        cout << (ans - 1 + MOD) % MOD << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值