【题解】Luogu-P2155
\(\text{Description}\)
-
给定 \(t,r\),有 \(t\) 次询问,每次给定 \(n,m\),请求出 \([1,n!]\) 中与 \(m!\) 互质的数的数量,答案对 \(r\) 取模.
-
对于 \(100\%\) 的数据,\(1\le m\le n\le 10^7,1\le t\le 10^4,2\le r\le 10^9+10\) 且 \(r\) 为质数.
\(\text{Solution}\)
首先因为 \(m\le n\),所以 \(m!\mid n!\),则与 \(m!\) 互质的有 \(\dfrac{n!}{m!}\cdot\varphi(m!)\) 个.
我们来整理一下:
因为 \(m!=1\times 2\times \cdots\times m\),所以质数 \(p\) 要整除 \(m!\) 实际上就是要满足质数 \(p\le m\).
所以就是
看起来预处理阶乘,前缀积和逆元,然后询问时三个一乘就完事了.
求连续 \(n\) 个数的逆元见 逆元.
具体地,用 \(jc_x\) 表示 \(x!\),\(phi_x\) 表示 \(\prod\limits_{前x个质数p}(p-1)\)(好吧我知道这不是 \(phi\) 但我们暂且叫它 \(phi\)),\(inv_x\) 表示 \(inv\left(\prod\limits_{前x个质数p}p\right)\).
查询时用 \(\operatorname{upper\_bound}\) 找到质数中第一个大于 \(m\) 的,则在这之前的所有质数必然都是小于等于 \(m\) 的,记它的位置是 \(y\),\(x=y-1\),然后答案就是 \((jc_n\cdot phi_x\cdot inv_x)\bmod r\).
scanf("%d%d", &n, &m);
int x = upper_bound(p + 1, p + p[0] + 1, m) - (p + 1);
printf("%d\n", 1ll * jc[n] * phi[x] % r * inv[x] % r);
然而兔队提供了 \(\text{hack}\) 数据,\(\text{hack}\) 的原理是,当分子与分母中含有相同个因数 \(r\) 时,正常计算会约分掉,但程序中如果把三个乘起来的话这三个中有 \(0\),算出来就是 \(0\),这是错的.
所以重点在于考虑如何处理这种情况.
因为 \(r\) 为质数,所以 \(\prod\limits_{质数p\le m}(p-1)\) 不可能含有因数 \(r\),所以当 \(n!\) 中因数 \(r\) 的个数比 \(\prod\limits_{质数p\le m}p\) 多的话,约分后还是 \(r\) 的倍数,输出 \(0\).
否则说明它们的因数 \(r\) 的个数一定相等!那么所有 \(r\) 都会被约掉.
在求阶乘和逆元时遇到 \(r\) 就不乘.
这样算还得特判 \(m=1\) 的情况,答案为 \(jc_n\).
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 1e7 + 5;
int p[MAXN];
bool vis[MAXN];
void get_prime()
{
for (int i = 2; i < MAXN; i++)
{
if (!vis[i])
{
p[++p[0]] = i;
}
for (int j = 1; j <= p[0] && i * p[j] < MAXN; j++)
{
vis[i * p[j]] = true;
if (i % p[j] == 0)
{
break;
}
}
}
}
int t, r, n, m;
int ksm(int a, int b)
{
int base = a, ans = 1;
while (b)
{
if (b & 1)
{
ans = 1ll * ans * base % r;
}
base = 1ll * base * base % r;
b >>= 1;
}
return ans;
}
int jc[MAXN];
void get_jc()
{
jc[0] = 1;
for (int i = 1; i < MAXN; i++)
{
if (i != r)
{
jc[i] = 1ll * jc[i - 1] * i % r;
}
else
{
jc[i] = jc[i - 1];
}
}
}
int phi[MAXN];
void get_phi()
{
phi[0] = 1;
for (int i = 1; i <= p[0]; i++)
{
phi[i] = 1ll * phi[i - 1] * (p[i] - 1) % r;
}
}
int pro[MAXN], inv[MAXN];
void get_inv()
{
pro[0] = 1;
for (int i = 1; i <= p[0]; i++)
{
if (p[i] != r)
{
pro[i] = 1ll * pro[i - 1] * p[i] % r;
}
else
{
pro[i] = pro[i - 1];
}
}
inv[p[0]] = ksm(pro[p[0]], r - 2);
for (int i = p[0] - 1; i >= 1; i--)
{
if (p[i + 1] != r)
{
inv[i] = 1ll * inv[i + 1] * p[i + 1] % r;
}
else
{
inv[i] = inv[i + 1];
}
}
}
int main()
{
scanf("%d%d", &t, &r);
get_prime();
get_jc();
get_phi();
get_inv();
while (t--)
{
scanf("%d%d", &n, &m);
if (n / r > m / r)
{
puts("0");
continue;
}
else if (m == 1)
{
printf("%d\n", jc[n]);
continue;
}
int x = upper_bound(p + 1, p + p[0] + 1, m) - (p + 1);
printf("%d\n", 1ll * jc[n] * phi[x] % r * inv[x] % r);
}
return 0;
}