给定正整数 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!=1∗2∗3∗...∗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(1−p11)(1−p21)...(1−pk1)
证明:
φ
(
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) :
-
如果 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} (n−1)!=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(n−1)=(n−1)!×(1−p11)(1−p21)...(1−pk1)
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!×(1−p11)(1−p21)...(1−pk1)(1−n1)=f(n−1)(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} (n−1)!=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(n−1)=(n−1)!×(1−p11)(1−p21)...(1−pk1)
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!×(1−p11)(1−p21)...(1−pk1)=f(n−1)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;
}