数学题都是屑题。
题解
题目要求 (sum_{i=1}^{n!}gcd(i,m!)=1) 这个式子。
我们先想简单的,我们会求 (sum_{i=1}^{m!}gcd(i,m!)=1),即 (φ(m!))。
考虑到 (m<=n),所以 (n!) 一定是 (m!) 的倍数,所以我们可以将 (n!) 分成 (frac{n!}{m!}) 段 (m!),而每一段有 (φ(m!)) 个数与 (m!) 互质,所以总共有 (frac{n!}{m!}*φ(m!)) 个数与 (m!) 互质,答案就是 (frac{n!}{m!}*φ(m!)) 。
上面的推理依据于一个定理:若 (gcd(a,b)=1),则 (gcd(a+b*k,b)=1) 。
证明:
我们可以利用 (gcd) 的性质来证:(gcd(a,b)=gcd(b,a\%b)=gcd(a\%b,b)),那么我们反着推回去即可得到:(gcd(a,b)=gcd(a+b*k,b)),证毕。
还有一个定理:若 (m=p_1^{q_1}p_2^{q_2}p_3^{q_3}...p_k^{q_k}(p_1,p_2,...,p_k)均为质数()),则 (m!) 的质因子仅有 (p_1,p_2,p_3...p_k) 。
这样我们再对答案式子进行化简:
(ans=frac{n!}{m!}*φ(m!)=frac{n!}{m!}*m!*frac{p_1-1}{p_1}*frac{p_2-1}{p_2}*...*frac{p_k-1}{p_k}=n!*prod_{i=1}^{k}frac{p_i-1}{p_i})
然后我们可以预处理 (1e7) 范围内的 (n!) 和 (prod_{i=1}^{k}frac{p_i-1}{p_i}),对于每次询问直接 (O(1)) 输出答案 。
(jc[i]) 表示 (i!) 是多少。
(inv[i]) 表示 (i) 的逆元是多少。
(ans[i]) 表示当上面的 (m=i) 时,(prod_{i=1}^{k}frac{p_i-1}{p_i}) 是多少。
求逆元的话需要线性的复杂度,这里再复习一下怎么求(主要是我也忘了(qwq)):
我们设 (p=k*i+r(1<r<i<p)),(k) 就是 (p/i) 的商,(r) 是余数。
则有 (k*i+requiv0(mod) (p)),两边同乘 (i^{-1}*r^{-1}) 得:
(k*r^{-1}+i^{-1}equiv0(mod) (p))
(i^{-1}equiv-k*r^{-1}(mod) (p))
(i^{-1}equiv-lfloor frac{p}{i}
floor*(p) (mod) (i)^{-1} (mod) (p))
inv[1] = 1;
for(int i = 2; i < p; ++ i)
inv[i] = (p - p / i) * inv[p % i] % p;
对于 (ans[i]) 的维护,我们在枚举 (i) 的时候如果发现 (i) 是质数,就让 (ans[i]=ans[i-1]*frac{i-1}{i});否则的话 (ans[i]=ans[i-1])。
判断质数的过程我们可以用欧拉筛来实现。
时间复杂度 (O(n)),稍微有点卡常(....)
(Code:)
#pragma GCC optimize(1) //手动开O(3)卡常qwq
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<stack>
#define ll long long
using namespace std;
const ll N=1e7+5;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<1)+(a<<3)+(ch^48);
ch=getchar();
}
return a*x;
}
int T,n,m,sum;
ll M,jc[N],inv[N],ans[N],prime[N];
bool is_prime[N];
int main()
{
T=read();M=(ll)read();
inv[0]=1;inv[1]=1;
ans[0]=1;ans[1]=1;
jc[0]=1;jc[1]=1;
for(int i=2;i<=1e7;i++) //预处理1e7内的信息
{
jc[i]=jc[i-1]*i%M; //朴素算阶乘
inv[i]=inv[M%i]*(M-M/i)%M; //线性求逆元
ans[i]=ans[i-1]%M; //维护ans数组
if(is_prime[i]==0) //如果i是质数的话,要多乘一个(i-1)/i
{
prime[++sum]=i;
ans[i]=ans[i]%M*(i-1)%M*inv[i]%M; //不能直接除以i,要乘i的逆元
}
for(int j=1;j<=sum&&prime[j]*i<=1e7;j++) //欧拉筛素数的过程
{
is_prime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
while(T--)
{
n=read();m=read();
printf("%lld
",jc[n]%M*ans[m]%M); //直接输出答案
}
return 0;
}