P2155 [SDOI2008]沙拉公主的困惑
数学题都是屑题。
题解
题目要求 \(\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+r\equiv0(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} \rfloor*(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\n",jc[n]%M*ans[m]%M); //直接输出答案
}
return 0;
}